diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline')
6 files changed, 252 insertions, 49 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts index 93b0932042..e0316a5517 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts @@ -1,4 +1,4 @@ -import { kTextureFormatInfo } from '../../../format_info.js'; +import { ColorTextureFormat, kTextureFormatInfo } from '../../../format_info.js'; import { getFragmentShaderCodeWithOutput, getPlainTypeInfo, @@ -6,12 +6,14 @@ import { } from '../../../util/shader.js'; import { ValidationTest } from '../validation_test.js'; +type ColorTargetState = GPUColorTargetState & { format: ColorTextureFormat }; + const values = [0, 1, 0, 1]; export class CreateRenderPipelineValidationTest extends ValidationTest { getDescriptor( options: { primitive?: GPUPrimitiveState; - targets?: GPUColorTargetState[]; + targets?: ColorTargetState[]; multisample?: GPUMultisampleState; depthStencil?: GPUDepthStencilState; fragmentShaderCode?: string; @@ -19,17 +21,16 @@ export class CreateRenderPipelineValidationTest extends ValidationTest { fragmentConstants?: Record<string, GPUPipelineConstantValue>; } = {} ): GPURenderPipelineDescriptor { - const defaultTargets: GPUColorTargetState[] = [{ format: 'rgba8unorm' }]; const { primitive = {}, - targets = defaultTargets, + targets = [{ format: 'rgba8unorm' }] as const, multisample = {}, depthStencil, fragmentShaderCode = getFragmentShaderCodeWithOutput([ { values, plainType: getPlainTypeInfo( - kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].sampleType + kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].color.type ), componentCount: 4, }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts index eaaf78af66..403f463943 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts @@ -5,7 +5,11 @@ This test dedicatedly tests validation of GPUDepthStencilState of createRenderPi import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { unreachable } from '../../../../common/util/util.js'; import { kCompareFunctions, kStencilOperations } from '../../../capability_info.js'; -import { kTextureFormats, kTextureFormatInfo, kDepthStencilFormats } from '../../../format_info.js'; +import { + kAllTextureFormats, + kTextureFormatInfo, + kDepthStencilFormats, +} from '../../../format_info.js'; import { getFragmentShaderCodeWithOutput } from '../../../util/shader.js'; import { CreateRenderPipelineValidationTest } from './common.js'; @@ -14,7 +18,11 @@ export const g = makeTestGroup(CreateRenderPipelineValidationTest); g.test('format') .desc(`The texture format in depthStencilState must be a depth/stencil format.`) - .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats)) + .params(u => + u // + .combine('isAsync', [false, true]) + .combine('format', kAllTextureFormats) + ) .beforeAllSubcases(t => { const { format } = t.params; const info = kTextureFormatInfo[format]; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts index 0206431eee..c01c2ba9ef 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts @@ -10,15 +10,17 @@ import { kMaxColorAttachmentsToTest, } from '../../../capability_info.js'; import { - kTextureFormats, + kAllTextureFormats, kRenderableColorTextureFormats, kTextureFormatInfo, computeBytesPerSampleFromFormats, + kColorTextureFormats, } from '../../../format_info.js'; import { getFragmentShaderCodeWithOutput, getPlainTypeInfo, kDefaultFragmentShaderCode, + kDefaultVertexShaderCode, } from '../../../util/shader.js'; import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js'; @@ -49,9 +51,60 @@ g.test('color_target_exists') t.doCreateRenderPipelineTest(isAsync, false, badDescriptor); }); +g.test('targets_format_is_color_format') + .desc( + `Tests that color target state format must be a color format, regardless of how the + fragment shader writes to it.` + ) + .params(u => + u + // Test all non-color texture formats, plus 'rgba8unorm' as a control case. + .combine('format', kAllTextureFormats) + .filter(({ format }) => { + return format === 'rgba8unorm' || !kTextureFormatInfo[format].color; + }) + .combine('isAsync', [false, true]) + .beginSubcases() + .combine('fragOutType', ['f32', 'u32', 'i32'] as const) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.skipIfTextureFormatNotSupported(t.params.format); + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(t => { + const { isAsync, format, fragOutType } = t.params; + + const fragmentShaderCode = getFragmentShaderCodeWithOutput([ + { values, plainType: fragOutType, componentCount: 4 }, + ]); + + const success = format === 'rgba8unorm' && fragOutType === 'f32'; + t.doCreateRenderPipelineTest(isAsync, success, { + vertex: { + module: t.device.createShaderModule({ code: kDefaultVertexShaderCode }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ code: fragmentShaderCode }), + entryPoint: 'main', + targets: [{ format }], + }, + layout: 'auto', + }); + }); + g.test('targets_format_renderable') - .desc(`Tests that color target state format must have RENDER_ATTACHMENT capability.`) - .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats)) + .desc( + `Tests that color target state format must have RENDER_ATTACHMENT capability + (tests only color formats).` + ) + .params(u => + u // + .combine('isAsync', [false, true]) + .combine('format', kColorTextureFormats) + ) .beforeAllSubcases(t => { const { format } = t.params; const info = kTextureFormatInfo[format]; @@ -158,24 +211,12 @@ g.test('limits,maxColorAttachmentBytesPerSample,unaligned') // become 4 and 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however // is allowed: 4+8+16+1+1 < 32. { - formats: [ - 'r8unorm', - 'r32float', - 'rgba8unorm', - 'rgba32float', - 'r8unorm', - ] as GPUTextureFormat[], + formats: ['r8unorm', 'r32float', 'rgba8unorm', 'rgba32float', 'r8unorm'], }, { - formats: [ - 'r32float', - 'rgba8unorm', - 'rgba32float', - 'r8unorm', - 'r8unorm', - ] as GPUTextureFormat[], + formats: ['r32float', 'rgba8unorm', 'rgba32float', 'r8unorm', 'r8unorm'], }, - ]) + ] as const) .beginSubcases() .combine('isAsync', [false, true]) ) diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts index 91aabb0ab8..db65ba8bf4 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts @@ -3,7 +3,7 @@ Interface matching between vertex and fragment shader validation for createRende `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { assert, range } from '../../../../common/util/util.js'; +import { range } from '../../../../common/util/util.js'; import { CreateRenderPipelineValidationTest } from './common.js'; @@ -97,8 +97,18 @@ g.test('location,mismatch') }); g.test('location,superset') - .desc(`TODO: implement after spec is settled: https://github.com/gpuweb/gpuweb/issues/2038`) - .unimplemented(); + .desc(`Tests that validation should succeed when vertex output is superset of fragment input`) + .params(u => u.combine('isAsync', [false, true])) + .fn(t => { + const { isAsync } = t.params; + + const descriptor = t.getDescriptorWithStates( + t.getVertexStateWithOutputs(['@location(0) vout0: f32', '@location(1) vout1: f32']), + t.getFragmentStateWithInputs(['@location(1) fin1: f32']) + ); + + t.doCreateRenderPipelineTest(isAsync, true, descriptor); + }); g.test('location,subset') .desc(`Tests that validation should fail when vertex output is a subset of fragment input.`) @@ -159,20 +169,27 @@ g.test('interpolation_type') { output: '@interpolate(linear)', input: '@interpolate(perspective)' }, { output: '@interpolate(flat)', input: '@interpolate(perspective)' }, { output: '@interpolate(linear)', input: '@interpolate(flat)' }, - { output: '@interpolate(linear, center)', input: '@interpolate(linear, center)' }, + { + output: '@interpolate(linear, center)', + input: '@interpolate(linear, center)', + _compat_success: false, + }, ]) ) .fn(t => { - const { isAsync, output, input, _success } = t.params; + const { isAsync, output, input, _success, _compat_success } = t.params; const descriptor = t.getDescriptorWithStates( t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]), t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`]) ); - t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor); - }); + const shouldSucceed = + (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false); + t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor); + }); +1; g.test('interpolation_sampling') .desc( `Tests that validation should fail when interpolation sampling of vertex output and fragment input at the same location doesn't match.` @@ -186,7 +203,12 @@ g.test('interpolation_sampling') input: '@interpolate(perspective, center)', _success: true, }, - { output: '@interpolate(linear, center)', input: '@interpolate(linear)', _success: true }, + { + output: '@interpolate(linear, center)', + input: '@interpolate(linear)', + _success: true, + _compat_success: false, + }, { output: '@interpolate(flat)', input: '@interpolate(flat)' }, { output: '@interpolate(perspective)', input: '@interpolate(perspective, sample)' }, { output: '@interpolate(perspective, center)', input: '@interpolate(perspective, sample)' }, @@ -198,14 +220,17 @@ g.test('interpolation_sampling') ]) ) .fn(t => { - const { isAsync, output, input, _success } = t.params; + const { isAsync, output, input, _success, _compat_success } = t.params; const descriptor = t.getDescriptorWithStates( t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]), t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`]) ); - t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor); + const shouldSucceed = + (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false); + + t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor); }); g.test('max_shader_variable_location') @@ -251,9 +276,6 @@ g.test('max_components_count,output') const numVec4 = Math.floor(numScalarComponents / 4); const numTrailingScalars = numScalarComponents % 4; - const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4; - - assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables); const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`); const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`); @@ -280,23 +302,23 @@ g.test('max_components_count,input') .params(u => u.combine('isAsync', [false, true]).combineWithParams([ // Number of user-defined input scalar components in test shader = device.limits.maxInterStageShaderComponents + numScalarDelta. - { numScalarDelta: 0, useExtraBuiltinInputs: false, _success: true }, - { numScalarDelta: 1, useExtraBuiltinInputs: false, _success: false }, - { numScalarDelta: 0, useExtraBuiltinInputs: true, _success: false }, - { numScalarDelta: -3, useExtraBuiltinInputs: true, _success: true }, - { numScalarDelta: -2, useExtraBuiltinInputs: true, _success: false }, + { numScalarDelta: 0, useExtraBuiltinInputs: false }, + { numScalarDelta: 1, useExtraBuiltinInputs: false }, + { numScalarDelta: 0, useExtraBuiltinInputs: true }, + { numScalarDelta: -3, useExtraBuiltinInputs: true }, + { numScalarDelta: -2, useExtraBuiltinInputs: true }, ] as const) ) .fn(t => { - const { isAsync, numScalarDelta, useExtraBuiltinInputs, _success } = t.params; + const { isAsync, numScalarDelta, useExtraBuiltinInputs } = t.params; const numScalarComponents = t.device.limits.maxInterStageShaderComponents + numScalarDelta; + const numExtraComponents = useExtraBuiltinInputs ? (t.isCompatibility ? 2 : 3) : 0; + const numUsedComponents = numScalarComponents + numExtraComponents; + const success = numUsedComponents <= t.device.limits.maxInterStageShaderComponents; const numVec4 = Math.floor(numScalarComponents / 4); const numTrailingScalars = numScalarComponents % 4; - const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4; - - assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables); const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`); const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`); @@ -310,9 +332,11 @@ g.test('max_components_count,input') if (useExtraBuiltinInputs) { inputs.push( '@builtin(front_facing) front_facing_in: bool', - '@builtin(sample_index) sample_index_in: u32', '@builtin(sample_mask) sample_mask_in: u32' ); + if (!t.isCompatibility) { + inputs.push('@builtin(sample_index) sample_index_in: u32'); + } } const descriptor = t.getDescriptorWithStates( @@ -320,5 +344,5 @@ g.test('max_components_count,input') t.getFragmentStateWithInputs(inputs, true) ); - t.doCreateRenderPipelineTest(isAsync, _success, descriptor); + t.doCreateRenderPipelineTest(isAsync, success, descriptor); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts index 1e3ccf5637..adb091a236 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts @@ -96,3 +96,37 @@ g.test('pipeline_layout,device_mismatch') t.doCreateRenderPipelineTest(isAsync, !mismatched, descriptor); }); + +g.test('external_texture') + .desc('Tests createRenderPipeline() with an external_texture') + .fn(t => { + const shader = t.device.createShaderModule({ + code: ` + @vertex + fn vertexMain() -> @builtin(position) vec4f { + return vec4f(1); + } + + @group(0) @binding(0) var myTexture: texture_external; + + @fragment + fn fragmentMain() -> @location(0) vec4f { + let result = textureLoad(myTexture, vec2u(1, 1)); + return vec4f(1); + } + `, + }); + + const descriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module: shader, + }, + fragment: { + module: shader, + targets: [{ format: 'rgba8unorm' }], + }, + }; + + t.doCreateRenderPipelineTest(false, true, descriptor); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts new file mode 100644 index 0000000000..5d6bc8d125 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts @@ -0,0 +1,95 @@ +export const description = ` +Tests for resource compatibilty between pipeline layout and shader modules + `; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../common/util/data_tables.js'; +import { + kAPIResources, + getWGSLShaderForResource, + getAPIBindGroupLayoutForResource, + doResourcesMatch, +} from '../utils.js'; + +import { CreateRenderPipelineValidationTest } from './common.js'; + +export const g = makeTestGroup(CreateRenderPipelineValidationTest); + +g.test('resource_compatibility') + .desc( + 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader' + ) + .params(u => + u // + .combine('stage', ['vertex', 'fragment'] as const) + .combine('apiResource', keysOf(kAPIResources)) + .filter(t => { + const res = kAPIResources[t.apiResource]; + if (t.stage === 'vertex') { + if (res.buffer && res.buffer.type === 'storage') { + return false; + } + if (res.storageTexture && res.storageTexture.access !== 'read-only') { + return false; + } + } + return true; + }) + .beginSubcases() + .combine('isAsync', [true, false] as const) + .combine('wgslResource', keysOf(kAPIResources)) + ) + .fn(t => { + const apiResource = kAPIResources[t.params.apiResource]; + const wgslResource = kAPIResources[t.params.wgslResource]; + t.skipIf( + wgslResource.storageTexture !== undefined && + wgslResource.storageTexture.access !== 'write-only' && + !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'), + 'Storage textures require language feature' + ); + const emptyVS = ` +@vertex +fn main() -> @builtin(position) vec4f { + return vec4f(); +} +`; + const emptyFS = ` +@fragment +fn main() -> @location(0) vec4f { + return vec4f(); +} +`; + + const code = getWGSLShaderForResource(t.params.stage, wgslResource); + const vsCode = t.params.stage === 'vertex' ? code : emptyVS; + const fsCode = t.params.stage === 'fragment' ? code : emptyFS; + const gpuStage: GPUShaderStageFlags = + t.params.stage === 'vertex' ? GPUShaderStage.VERTEX : GPUShaderStage.FRAGMENT; + const layout = t.device.createPipelineLayout({ + bindGroupLayouts: [getAPIBindGroupLayoutForResource(t.device, gpuStage, apiResource)], + }); + + const descriptor = { + layout, + vertex: { + module: t.device.createShaderModule({ + code: vsCode, + }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ + code: fsCode, + }), + entryPoint: 'main', + targets: [{ format: 'rgba8unorm' }] as const, + }, + }; + + t.doCreateRenderPipelineTest( + t.params.isAsync, + doResourcesMatch(apiResource, wgslResource), + descriptor + ); + }); |