From 9cf0129e51b25c16310830dc040adb444fede64e Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Fri, 2 Aug 2024 16:53:45 +0800 Subject: [PATCH] Add validation tests on the access of the alpha channel of Src1 (#3886) * Add validation tests on the access of the alpha channel of Src1 This patch adds validation tests on the use of `src1-alpha` in the blend factor. When the blend factor uses the alpha channel of src1, the fragment output must be vec4. This patch also implements function `IsDualSourceBlendingFactor()` to check if a blend factor needs the extension "dual-source-blending". This patch also adds the support of dual source blending in the helper function `getFragmentShaderCodeWithOutput()` to simplify the shaders used in dual source blending tests. * Fix `IsDualSourceBlendingFactor()` * Allow `IsDualSourceBlendingFactor()` accepts null value as parameter * More fixes --- .../rendering/color_target_state.spec.ts | 10 +- .../render_pipeline/fragment_state.spec.ts | 111 ++++++++---------- src/webgpu/capability_info.ts | 21 ++-- src/webgpu/util/shader.ts | 19 ++- 4 files changed, 82 insertions(+), 79 deletions(-) diff --git a/src/webgpu/api/operation/rendering/color_target_state.spec.ts b/src/webgpu/api/operation/rendering/color_target_state.spec.ts index b5db227caa4d..5923cc1b063d 100644 --- a/src/webgpu/api/operation/rendering/color_target_state.spec.ts +++ b/src/webgpu/api/operation/rendering/color_target_state.spec.ts @@ -10,8 +10,8 @@ TODO: import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert, TypedArrayBufferView, unreachable } from '../../../../common/util/util.js'; import { + IsDualSourceBlendingFactor, kBlendFactors, - kDualSourceBlendingFactorsSet, kBlendOperations, } from '../../../capability_info.js'; import { GPUConst } from '../../../constants.js'; @@ -203,8 +203,8 @@ g.test('blending,GPUBlendComponent') ) .beforeAllSubcases(t => { if ( - kDualSourceBlendingFactorsSet.has(t.params.srcFactor) || - kDualSourceBlendingFactorsSet.has(t.params.dstFactor) + IsDualSourceBlendingFactor(t.params.srcFactor) || + IsDualSourceBlendingFactor(t.params.dstFactor) ) { t.selectDeviceOrSkipTestCase('dual-source-blending'); } @@ -251,8 +251,8 @@ g.test('blending,GPUBlendComponent') } const useBlendSrc1 = - kDualSourceBlendingFactorsSet.has(t.params.srcFactor) || - kDualSourceBlendingFactorsSet.has(t.params.dstFactor); + IsDualSourceBlendingFactor(t.params.srcFactor) || + IsDualSourceBlendingFactor(t.params.dstFactor); const pipeline = t.device.createRenderPipeline({ layout: 'auto', diff --git a/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts b/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts index 43ac6464b7c5..51a5d5f79763 100644 --- a/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts +++ b/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts @@ -5,8 +5,8 @@ This test dedicatedly tests validation of GPUFragmentState of createRenderPipeli import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert, range } from '../../../../common/util/util.js'; import { + IsDualSourceBlendingFactor, kBlendFactors, - kDualSourceBlendingFactorsSet, kBlendOperations, kMaxColorAttachmentsToTest, } from '../../../capability_info.js'; @@ -298,10 +298,7 @@ g.test('targets_blend') ) .beforeAllSubcases(t => { const { srcFactor, dstFactor } = t.params; - if ( - kDualSourceBlendingFactorsSet.has(srcFactor) || - kDualSourceBlendingFactorsSet.has(dstFactor) - ) { + if (IsDualSourceBlendingFactor(srcFactor) || IsDualSourceBlendingFactor(dstFactor)) { t.selectDeviceOrSkipTestCase('dual-source-blending'); } }) @@ -320,20 +317,12 @@ g.test('targets_blend') }; const format = 'rgba8unorm'; const useDualSourceBlending = - kDualSourceBlendingFactorsSet.has(srcFactor) || kDualSourceBlendingFactorsSet.has(dstFactor); - const fragmentShaderCode = useDualSourceBlending - ? `enable dual_source_blending; - - struct FragOutput { - @location(0) @blend_src(0) color : vec4f, - @location(0) @blend_src(1) blend : vec4f, - } - - @fragment fn main() -> FragOutput { - var fragmentOutput : FragOutput; - return fragmentOutput; - }` - : undefined; + IsDualSourceBlendingFactor(srcFactor) || IsDualSourceBlendingFactor(dstFactor); + const fragmentShaderCode = getFragmentShaderCodeWithOutput( + [{ values, plainType: 'f32', componentCount: 4 }], + null, + useDualSourceBlending + ); const descriptor = t.getDescriptor({ targets: [ @@ -437,9 +426,6 @@ g.test('pipeline_output_targets,blend') `On top of requirements from pipeline_output_targets, when blending is enabled and alpha channel is read indicated by any color blend factor, an extra requirement is added: - fragment output must be vec4. - - TODO: Implement all the skipped tests about the blend factors added in the extension - "dual-source-blending" ` ) .params(u => @@ -454,18 +440,23 @@ g.test('pipeline_output_targets,blend') ...u.combine('alphaSrcFactor', kBlendFactors), ...u.combine('alphaDstFactor', kBlendFactors), ] as const) - .unless( - p => - (p.colorSrcFactor !== undefined && kDualSourceBlendingFactorsSet.has(p.colorSrcFactor)) || - (p.colorDstFactor !== undefined && kDualSourceBlendingFactorsSet.has(p.colorDstFactor)) || - (p.alphaSrcFactor !== undefined && kDualSourceBlendingFactorsSet.has(p.alphaSrcFactor)) || - (p.alphaDstFactor !== undefined && kDualSourceBlendingFactorsSet.has(p.alphaDstFactor)) - ) ) .beforeAllSubcases(t => { - const { format } = t.params; + const { format, colorSrcFactor, colorDstFactor, alphaSrcFactor, alphaDstFactor } = t.params; + const info = kTextureFormatInfo[format]; - t.selectDeviceOrSkipTestCase(info.feature); + const requiredFeatures: (GPUFeatureName | undefined)[] = [info.feature]; + + if ( + IsDualSourceBlendingFactor(colorSrcFactor) || + IsDualSourceBlendingFactor(colorDstFactor) || + IsDualSourceBlendingFactor(alphaSrcFactor) || + IsDualSourceBlendingFactor(alphaDstFactor) + ) { + requiredFeatures.push('dual-source-blending'); + } + + t.selectDeviceOrSkipTestCase(requiredFeatures); }) .fn(t => { const sampleType = 'float'; @@ -480,6 +471,12 @@ g.test('pipeline_output_targets,blend') } = t.params; const info = kTextureFormatInfo[format]; + const useDualSourceBlending = + IsDualSourceBlendingFactor(colorSrcFactor) || + IsDualSourceBlendingFactor(colorDstFactor) || + IsDualSourceBlendingFactor(alphaSrcFactor) || + IsDualSourceBlendingFactor(alphaDstFactor); + const descriptor = t.getDescriptor({ targets: [ { @@ -490,13 +487,18 @@ g.test('pipeline_output_targets,blend') }, }, ], - fragmentShaderCode: getFragmentShaderCodeWithOutput([ - { values, plainType: getPlainTypeInfo(sampleType), componentCount }, - ]), + fragmentShaderCode: getFragmentShaderCodeWithOutput( + [{ values, plainType: getPlainTypeInfo(sampleType), componentCount }], + null, + useDualSourceBlending + ), }); const colorBlendReadsSrcAlpha = - colorSrcFactor?.includes('src-alpha') || colorDstFactor?.includes('src-alpha'); + colorSrcFactor?.includes('src-alpha') || + colorDstFactor?.includes('src-alpha') || + colorSrcFactor?.includes('src1-alpha') || + colorDstFactor?.includes('src1-alpha'); const meetsExtraBlendingRequirement = !colorBlendReadsSrcAlpha || componentCount === 4; const _success = info.color.type === sampleType && @@ -564,21 +566,11 @@ g.test('dual_source_blending,color_target_count') const descriptor = t.getDescriptor({ targets: colorTargetStates, - fragmentShaderCode: ` - enable dual_source_blending; - - struct FragOutput { - @location(0) @blend_src(0) color : vec4f, - @location(0) @blend_src(1) blend : vec4f, - } - - @fragment fn main() -> FragOutput { - var fragmentOutput : FragOutput; - fragmentOutput.color = vec4f(0.0, 1.0, 0.0, 1.0); - fragmentOutput.blend = fragmentOutput.color; - return fragmentOutput; - } - `, + fragmentShaderCode: getFragmentShaderCodeWithOutput( + [{ values, plainType: 'f32', componentCount: 4 }], + null, + true + ), }); const isAsync = false; @@ -628,21 +620,14 @@ g.test('dual_source_blending,use_blend_src') writeMask, }, ], - fragmentShaderCode: ` - ${useBlendSrc1 ? 'enable dual_source_blending;' : ''} - struct FragOutput { - @location(0) ${useBlendSrc1 ? '@blend_src(0)' : ''} color : vec4f, - ${useBlendSrc1 ? '@location(0) @blend_src(1) blend : vec4f,' : ''} - } - @fragment fn main() -> FragOutput { - var fragmentOutput : FragOutput; - return fragmentOutput; - } - `, + fragmentShaderCode: getFragmentShaderCodeWithOutput( + [{ values, plainType: 'f32', componentCount: 4 }], + null, + useBlendSrc1 + ), }); - const kDualSourceBlendingFactorsSet = new Set(kDualSourceBlendingFactors); - const _success = !kDualSourceBlendingFactorsSet.has(blendFactor) || useBlendSrc1; + const _success = !IsDualSourceBlendingFactor(blendFactor) || useBlendSrc1; const isAsync = false; t.doCreateRenderPipelineTest(isAsync, _success, descriptor); }); diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts index 6d8c1589209c..24a103dfb4b1 100644 --- a/src/webgpu/capability_info.ts +++ b/src/webgpu/capability_info.ts @@ -665,17 +665,22 @@ export const kBlendFactors: readonly GPUBlendFactor[] = [ 'one-minus-src1-alpha', ]; -/** Set of all GPUBlendFactor values added in the extension "dual-source-blending". */ -export const kDualSourceBlendingFactorsSet = new Set([ - 'src1', - 'one-minus-src1', - 'src1-alpha', - 'one-minus-src1-alpha', -]); +/** Check if `blendFactor` belongs to the blend factors in the extension "dual-source-blending". */ +export function IsDualSourceBlendingFactor(blendFactor?: GPUBlendFactor): boolean { + switch (blendFactor) { + case 'src1': + case 'one-minus-src1': + case 'src1-alpha': + case 'one-minus-src1-alpha': + return true; + default: + return false; + } +} /** List of all GPUBlendOperation values. */ export const kBlendOperations: readonly GPUBlendOperation[] = [ - 'add', // + 'add', 'subtract', 'reverse-subtract', 'min', diff --git a/src/webgpu/util/shader.ts b/src/webgpu/util/shader.ts index 721d121abac4..aad0e3a97f82 100644 --- a/src/webgpu/util/shader.ts +++ b/src/webgpu/util/shader.ts @@ -1,4 +1,4 @@ -import { unreachable } from '../../common/util/util.js'; +import { assert, unreachable } from '../../common/util/util.js'; export const kDefaultVertexShaderCode = ` @vertex fn main() -> @builtin(position) vec4 { @@ -109,7 +109,8 @@ export function getFragmentShaderCodeWithOutput( plainType: 'i32' | 'u32' | 'f32'; componentCount: number; } | null)[], - fragDepth: { value: number } | null = null + fragDepth: { value: number } | null = null, + dualSourceBlending: boolean = false ): string { if (outputs.length === 0) { if (fragDepth) { @@ -165,10 +166,22 @@ export function getFragmentShaderCodeWithOutput( unreachable(); } - outputStructString += `@location(${i}) o${i} : ${outputType},\n`; + if (dualSourceBlending) { + assert(i === 0 && outputs.length === 1); + outputStructString += ` + @location(0) @blend_src(0) o0 : ${outputType}, + @location(0) @blend_src(1) o0_blend : ${outputType}, + `; + resultStrings.push(resultStrings[0]); + break; + } else { + outputStructString += `@location(${i}) o${i} : ${outputType},\n`; + } } return ` + ${dualSourceBlending ? 'enable dual_source_blending;' : ''} + struct Outputs { ${outputStructString} }