diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts | 392 |
1 files changed, 392 insertions, 0 deletions
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 new file mode 100644 index 0000000000..ffedddea2c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts @@ -0,0 +1,392 @@ +export const description = ` +This test dedicatedly tests validation of GPUFragmentState of createRenderPipeline. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { range } from '../../../../common/util/util.js'; +import { + kTextureFormats, + kRenderableColorTextureFormats, + kTextureFormatInfo, + kBlendFactors, + kBlendOperations, + kMaxColorAttachments, +} from '../../../capability_info.js'; +import { + getFragmentShaderCodeWithOutput, + getPlainTypeInfo, + kDefaultFragmentShaderCode, +} from '../../../util/shader.js'; +import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js'; + +import { CreateRenderPipelineValidationTest } from './common.js'; + +export const g = makeTestGroup(CreateRenderPipelineValidationTest); + +const values = [0, 1, 0, 1]; + +g.test('color_target_exists') + .desc(`Tests creating a complete render pipeline requires at least one color target state.`) + .params(u => u.combine('isAsync', [false, true])) + .fn(async t => { + const { isAsync } = t.params; + + const goodDescriptor = t.getDescriptor({ + targets: [{ format: 'rgba8unorm' }], + }); + + // Control case + t.doCreateRenderPipelineTest(isAsync, true, goodDescriptor); + + // Fail because lack of color states + const badDescriptor = t.getDescriptor({ + targets: [], + }); + + t.doCreateRenderPipelineTest(isAsync, false, badDescriptor); + }); + +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)) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { isAsync, format } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor = t.getDescriptor({ targets: [{ format }] }); + + t.doCreateRenderPipelineTest(isAsync, info.renderable && info.color, descriptor); + }); + +g.test('limits,maxColorAttachments') + .desc( + `Tests that color state targets length must not be larger than device.limits.maxColorAttachments.` + ) + .params(u => u.combine('isAsync', [false, true]).combine('targetsLength', [8, 9])) + .fn(async t => { + const { isAsync, targetsLength } = t.params; + + const descriptor = t.getDescriptor({ + targets: range(targetsLength, i => { + // Set writeMask to 0 for attachments without fragment output + return { format: 'rg8unorm', writeMask: i === 0 ? 0xf : 0 }; + }), + fragmentShaderCode: kDefaultFragmentShaderCode, + }); + + t.doCreateRenderPipelineTest( + isAsync, + targetsLength <= t.device.limits.maxColorAttachments, + descriptor + ); + }); + +g.test('limits,maxColorAttachmentBytesPerSample,aligned') + .desc( + ` + Tests that the total color attachment bytes per sample must not be larger than + maxColorAttachmentBytesPerSample when using the same format for multiple attachments. + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .beginSubcases() + .combine( + 'attachmentCount', + range(kMaxColorAttachments, i => i + 1) + ) + .combine('isAsync', [false, true]) + ) + .fn(async t => { + const { format, attachmentCount, isAsync } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor = t.getDescriptor({ + targets: range(attachmentCount, () => { + return { format, writeMask: 0 }; + }), + }); + const shouldError = + info.renderTargetPixelByteCost === undefined || + info.renderTargetPixelByteCost * attachmentCount > + t.device.limits.maxColorAttachmentBytesPerSample; + + t.doCreateRenderPipelineTest(isAsync, !shouldError, descriptor); + }); + +g.test('limits,maxColorAttachmentBytesPerSample,unaligned') + .desc( + ` + Tests that the total color attachment bytes per sample must not be larger than + maxColorAttachmentBytesPerSample when using various sets of (potentially) unaligned formats. + ` + ) + .params(u => + u + .combineWithParams([ + // Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though + // 1+4+8+16+1 < 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm + // 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[], + _success: true, + }, + { + formats: [ + 'r32float', + 'rgba8unorm', + 'rgba32float', + 'r8unorm', + 'r8unorm', + ] as GPUTextureFormat[], + _success: false, + }, + ]) + .beginSubcases() + .combine('isAsync', [false, true]) + ) + .fn(async t => { + const { formats, _success, isAsync } = t.params; + + const descriptor = t.getDescriptor({ + targets: formats.map(f => { + return { format: f, writeMask: 0 }; + }), + }); + + t.doCreateRenderPipelineTest(isAsync, _success, descriptor); + }); + +g.test('targets_format_filterable') + .desc(`Tests that color target state format must be filterable if blend is not undefined.`) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', kRenderableColorTextureFormats) + .beginSubcases() + .combine('hasBlend', [false, true]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { isAsync, format, hasBlend } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor = t.getDescriptor({ + targets: [ + { + format, + blend: hasBlend ? { color: {}, alpha: {} } : undefined, + }, + ], + }); + + t.doCreateRenderPipelineTest(isAsync, !hasBlend || info.sampleType === 'float', descriptor); + }); + +g.test('targets_blend') + .desc( + ` + For the blend components on either GPUBlendState.color or GPUBlendState.alpha: + - Tests if the combination of 'srcFactor', 'dstFactor' and 'operation' is valid (if the blend + operation is "min" or "max", srcFactor and dstFactor must be "one"). + ` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('component', ['color', 'alpha'] as const) + .beginSubcases() + .combine('srcFactor', kBlendFactors) + .combine('dstFactor', kBlendFactors) + .combine('operation', kBlendOperations) + ) + .fn(async t => { + const { isAsync, component, srcFactor, dstFactor, operation } = t.params; + + const defaultBlendComponent: GPUBlendComponent = { + srcFactor: 'src-alpha', + dstFactor: 'dst-alpha', + operation: 'add', + }; + const blendComponentToTest: GPUBlendComponent = { + srcFactor, + dstFactor, + operation, + }; + const format = 'rgba8unorm'; + + const descriptor = t.getDescriptor({ + targets: [ + { + format, + blend: { + color: component === 'color' ? blendComponentToTest : defaultBlendComponent, + alpha: component === 'alpha' ? blendComponentToTest : defaultBlendComponent, + }, + }, + ], + }); + + if (operation === 'min' || operation === 'max') { + const _success = srcFactor === 'one' && dstFactor === 'one'; + t.doCreateRenderPipelineTest(isAsync, _success, descriptor); + } else { + t.doCreateRenderPipelineTest(isAsync, true, descriptor); + } + }); + +g.test('targets_write_mask') + .desc(`Tests that color target state write mask must be < 16.`) + .params(u => u.combine('isAsync', [false, true]).combine('writeMask', [0, 0xf, 0x10, 0x80000001])) + .fn(async t => { + const { isAsync, writeMask } = t.params; + + const descriptor = t.getDescriptor({ + targets: [ + { + format: 'rgba8unorm', + writeMask, + }, + ], + }); + + t.doCreateRenderPipelineTest(isAsync, writeMask < 16, descriptor); + }); + +g.test('pipeline_output_targets') + .desc( + `Pipeline fragment output types must be compatible with target color state format + - The scalar type (f32, i32, or u32) must match the sample type of the format. + - The componentCount of the fragment output (e.g. f32, vec2, vec3, vec4) must not have fewer + channels than that of the color attachment texture formats. Extra components are allowed and are discarded. + + Otherwise, color state write mask must be 0.` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', [undefined, ...kRenderableColorTextureFormats] as const) + .beginSubcases() + .combine('shaderOutput', [ + undefined, + ...u.combine('scalar', ['f32', 'u32', 'i32'] as const).combine('count', [1, 2, 3, 4]), + ]) + // We only care about testing writeMask if there is an attachment but no shader output. + .expand('writeMask', p => + p.format !== undefined && p.shaderOutput !== undefined ? [0, 0x1, 0x2, 0x4, 0x8] : [0xf] + ) + ) + .beforeAllSubcases(t => { + t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); + }) + .fn(async t => { + const { isAsync, format, writeMask, shaderOutput } = t.params; + + const descriptor = t.getDescriptor({ + targets: format ? [{ format, writeMask }] : [], + // To have a dummy depthStencil attachment to avoid having no attachment at all which is invalid + depthStencil: { format: 'depth24plus' }, + fragmentShaderCode: getFragmentShaderCodeWithOutput( + shaderOutput + ? [{ values, plainType: shaderOutput.scalar, componentCount: shaderOutput.count }] + : [] + ), + }); + + let success = true; + if (format) { + // There is a color target + if (shaderOutput) { + // The shader outputs to the color target + const info = kTextureFormatInfo[format]; + success = + shaderOutput.scalar === getPlainTypeInfo(info.sampleType) && + shaderOutput.count >= kTexelRepresentationInfo[format].componentOrder.length; + } else { + // The shader does not output to the color target + success = writeMask === 0; + } + } + + t.doCreateRenderPipelineTest(isAsync, success, descriptor); + }); + +g.test('pipeline_output_targets,blend') + .desc( + `On top of requirements from pipeline_output_targets, when blending is enabled and alpha channel is read indicated by any blend factor, an extra requirement is added: + - fragment output must be vec4. + ` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', ['r8unorm', 'rg8unorm', 'rgba8unorm', 'bgra8unorm'] as const) + .combine('componentCount', [1, 2, 3, 4]) + .beginSubcases() + // The default srcFactor and dstFactor are 'one' and 'zero'. Override just one at a time. + .combineWithParams([ + ...u.combine('colorSrcFactor', kBlendFactors), + ...u.combine('colorDstFactor', kBlendFactors), + ...u.combine('alphaSrcFactor', kBlendFactors), + ...u.combine('alphaDstFactor', kBlendFactors), + ] as const) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const sampleType = 'float'; + const { + isAsync, + format, + componentCount, + colorSrcFactor, + colorDstFactor, + alphaSrcFactor, + alphaDstFactor, + } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor = t.getDescriptor({ + targets: [ + { + format, + blend: { + color: { srcFactor: colorSrcFactor, dstFactor: colorDstFactor }, + alpha: { srcFactor: alphaSrcFactor, dstFactor: alphaDstFactor }, + }, + }, + ], + fragmentShaderCode: getFragmentShaderCodeWithOutput([ + { values, plainType: getPlainTypeInfo(sampleType), componentCount }, + ]), + }); + + const colorBlendReadsSrcAlpha = + colorSrcFactor?.includes('src-alpha') || colorDstFactor?.includes('src-alpha'); + const meetsExtraBlendingRequirement = !colorBlendReadsSrcAlpha || componentCount === 4; + const _success = + info.sampleType === sampleType && + componentCount >= kTexelRepresentationInfo[format].componentOrder.length && + meetsExtraBlendingRequirement; + t.doCreateRenderPipelineTest(isAsync, _success, descriptor); + }); |