diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts | 890 |
1 files changed, 890 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts new file mode 100644 index 0000000000..a0afeff2d0 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts @@ -0,0 +1,890 @@ +export const description = ` +Test blending results. + +TODO: +- Test result for all combinations of args (make sure each case is distinguishable from others +- Test underflow/overflow has consistent behavior +- ? +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { assert, TypedArrayBufferView, unreachable } from '../../../../common/util/util.js'; +import { + kBlendFactors, + kBlendOperations, + kEncodableTextureFormats, + kTextureFormatInfo, +} from '../../../capability_info.js'; +import { GPUConst } from '../../../constants.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { float32ToFloat16Bits } from '../../../util/conversion.js'; +import { clamp } from '../../../util/math.js'; +import { TexelView } from '../../../util/texture/texel_view.js'; +import { textureContentIsOKByT2B } from '../../../util/texture/texture_ok.js'; + +class BlendingTest extends GPUTest { + createRenderPipelineForTest(colorTargetState: GPUColorTargetState): GPURenderPipeline { + return this.device.createRenderPipeline({ + layout: 'auto', + fragment: { + targets: [colorTargetState], + module: this.device.createShaderModule({ + code: ` + struct Params { + color : vec4<f32> + } + @group(0) @binding(0) var<uniform> params : Params; + @fragment fn main() -> @location(0) vec4<f32> { + return params.color; + } + `, + }), + entryPoint: 'main', + }, + vertex: { + module: this.device.createShaderModule({ + code: ` + @vertex fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1.0, -1.0), + vec2<f32>(3.0, -1.0), + vec2<f32>(-1.0, 3.0)); + return vec4<f32>(pos[VertexIndex], 0.0, 1.0); + } + `, + }), + entryPoint: 'main', + }, + }); + } + + createBindGroupForTest(layout: GPUBindGroupLayout, data: TypedArrayBufferView): GPUBindGroup { + return this.device.createBindGroup({ + layout, + entries: [ + { + binding: 0, + resource: { + buffer: this.makeBufferWithContents(data, GPUBufferUsage.UNIFORM), + }, + }, + ], + }); + } +} + +export const g = makeTestGroup(BlendingTest); + +function mapColor( + col: GPUColorDict, + f: (v: number, k: keyof GPUColorDict) => number +): GPUColorDict { + return { + r: f(col.r, 'r'), + g: f(col.g, 'g'), + b: f(col.b, 'b'), + a: f(col.a, 'a'), + }; +} + +function computeBlendFactor( + src: GPUColorDict, + dst: GPUColorDict, + blendColor: GPUColorDict | undefined, + factor: GPUBlendFactor +): GPUColorDict { + switch (factor) { + case 'zero': + return { r: 0, g: 0, b: 0, a: 0 }; + case 'one': + return { r: 1, g: 1, b: 1, a: 1 }; + case 'src': + return { ...src }; + case 'one-minus-src': + return mapColor(src, v => 1 - v); + case 'src-alpha': + return mapColor(src, () => src.a); + case 'one-minus-src-alpha': + return mapColor(src, () => 1 - src.a); + case 'dst': + return { ...dst }; + case 'one-minus-dst': + return mapColor(dst, v => 1 - v); + case 'dst-alpha': + return mapColor(dst, () => dst.a); + case 'one-minus-dst-alpha': + return mapColor(dst, () => 1 - dst.a); + case 'src-alpha-saturated': { + const f = Math.min(src.a, 1 - dst.a); + return { r: f, g: f, b: f, a: 1 }; + } + case 'constant': + assert(blendColor !== undefined); + return { ...blendColor }; + case 'one-minus-constant': + assert(blendColor !== undefined); + return mapColor(blendColor, v => 1 - v); + default: + unreachable(); + } +} + +function computeBlendOperation( + src: GPUColorDict, + srcFactor: GPUColorDict, + dst: GPUColorDict, + dstFactor: GPUColorDict, + operation: GPUBlendOperation +) { + switch (operation) { + case 'add': + return mapColor(src, (_, k) => srcFactor[k] * src[k] + dstFactor[k] * dst[k]); + case 'max': + return mapColor(src, (_, k) => Math.max(src[k], dst[k])); + case 'min': + return mapColor(src, (_, k) => Math.min(src[k], dst[k])); + case 'reverse-subtract': + return mapColor(src, (_, k) => dstFactor[k] * dst[k] - srcFactor[k] * src[k]); + case 'subtract': + return mapColor(src, (_, k) => srcFactor[k] * src[k] - dstFactor[k] * dst[k]); + } +} + +g.test('blending,GPUBlendComponent') + .desc( + `Test all combinations of parameters for GPUBlendComponent. + + Tests that parameters are correctly passed to the backend API and blend computations + are done correctly by blending a single pixel. The test uses rgba16float as the format + to avoid checking clamping behavior (tested in api,operation,rendering,blending:clamp,*). + + Params: + - component= {color, alpha} - whether to test blending the color or the alpha component. + - srcFactor= {...all GPUBlendFactors} + - dstFactor= {...all GPUBlendFactors} + - operation= {...all GPUBlendOperations}` + ) + .params(u => + u // + .combine('component', ['color', 'alpha'] as const) + .combine('srcFactor', kBlendFactors) + .combine('dstFactor', kBlendFactors) + .combine('operation', kBlendOperations) + .filter(t => { + if (t.operation === 'min' || t.operation === 'max') { + return t.srcFactor === 'one' && t.dstFactor === 'one'; + } + return true; + }) + .beginSubcases() + .combine('srcColor', [{ r: 0.11, g: 0.61, b: 0.81, a: 0.44 }]) + .combine('dstColor', [ + { r: 0.51, g: 0.22, b: 0.71, a: 0.33 }, + { r: 0.09, g: 0.73, b: 0.93, a: 0.81 }, + ]) + .expand('blendConstant', p => { + const needsBlendConstant = + p.srcFactor === 'one-minus-constant' || + p.srcFactor === 'constant' || + p.dstFactor === 'one-minus-constant' || + p.dstFactor === 'constant'; + return needsBlendConstant ? [{ r: 0.91, g: 0.82, b: 0.73, a: 0.64 }] : [undefined]; + }) + ) + .fn(t => { + const textureFormat: GPUTextureFormat = 'rgba16float'; + const srcColor = t.params.srcColor; + const dstColor = t.params.dstColor; + const blendConstant = t.params.blendConstant; + + const srcFactor = computeBlendFactor(srcColor, dstColor, blendConstant, t.params.srcFactor); + const dstFactor = computeBlendFactor(srcColor, dstColor, blendConstant, t.params.dstFactor); + + const expectedColor = computeBlendOperation( + srcColor, + srcFactor, + dstColor, + dstFactor, + t.params.operation + ); + + switch (t.params.component) { + case 'color': + expectedColor.a = srcColor.a; + break; + case 'alpha': + expectedColor.r = srcColor.r; + expectedColor.g = srcColor.g; + expectedColor.b = srcColor.b; + break; + } + + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + fragment: { + targets: [ + { + format: textureFormat, + blend: { + // Set both color/alpha to defaults... + color: {}, + alpha: {}, + // ... but then override the component we're testing. + [t.params.component]: { + srcFactor: t.params.srcFactor, + dstFactor: t.params.dstFactor, + operation: t.params.operation, + }, + }, + }, + ], + module: t.device.createShaderModule({ + code: ` +struct Uniform { + color: vec4<f32> +}; +@group(0) @binding(0) var<uniform> u : Uniform; + +@fragment fn main() -> @location(0) vec4<f32> { + return u.color; +} + `, + }), + entryPoint: 'main', + }, + vertex: { + module: t.device.createShaderModule({ + code: ` +@vertex fn main() -> @builtin(position) vec4<f32> { + return vec4<f32>(0.0, 0.0, 0.0, 1.0); +} + `, + }), + entryPoint: 'main', + }, + primitive: { + topology: 'point-list', + }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [1, 1, 1], + format: textureFormat, + }); + + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + clearValue: dstColor, + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(pipeline); + if (blendConstant) { + renderPass.setBlendConstant(blendConstant); + } + renderPass.setBindGroup( + 0, + t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: t.makeBufferWithContents( + new Float32Array([srcColor.r, srcColor.g, srcColor.b, srcColor.a]), + GPUBufferUsage.UNIFORM + ), + }, + }, + ], + }) + ); + renderPass.draw(1); + renderPass.end(); + + t.device.queue.submit([commandEncoder.finish()]); + + const tolerance = 0.003; + const expectedLow = mapColor(expectedColor, v => v - tolerance); + const expectedHigh = mapColor(expectedColor, v => v + tolerance); + + t.expectSinglePixelBetweenTwoValuesFloat16In2DTexture( + renderTarget, + textureFormat, + { x: 0, y: 0 }, + { + exp: [ + // Use Uint16Array to store Float16 value bits + new Uint16Array( + [expectedLow.r, expectedLow.g, expectedLow.b, expectedLow.a].map(float32ToFloat16Bits) + ), + new Uint16Array( + [expectedHigh.r, expectedHigh.g, expectedHigh.b, expectedHigh.a].map( + float32ToFloat16Bits + ) + ), + ], + } + ); + }); + +const kBlendableFormats = kEncodableTextureFormats.filter(f => { + const info = kTextureFormatInfo[f]; + return info.renderable && info.sampleType === 'float'; +}); + +g.test('blending,formats') + .desc( + `Test blending results works for all formats that support it, and that blending is not applied + for formats that do not. Blending should be done in linear space for srgb formats.` + ) + .params(u => + u // + .combine('format', kBlendableFormats) + ) + .fn(async t => { + const { format } = t.params; + + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + fragment: { + targets: [ + { + format, + blend: { + color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' }, + alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' }, + }, + }, + ], + module: t.device.createShaderModule({ + code: ` +@fragment fn main() -> @location(0) vec4<f32> { + return vec4<f32>(0.4, 0.4, 0.4, 0.4); +} + `, + }), + entryPoint: 'main', + }, + vertex: { + module: t.device.createShaderModule({ + code: ` +@vertex fn main() -> @builtin(position) vec4<f32> { + return vec4<f32>(0.0, 0.0, 0.0, 1.0); +} + `, + }), + entryPoint: 'main', + }, + primitive: { + topology: 'point-list', + }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [1, 1, 1], + format, + }); + + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + clearValue: { r: 0.2, g: 0.2, b: 0.2, a: 0.2 }, + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(pipeline); + renderPass.draw(1); + renderPass.end(); + t.device.queue.submit([commandEncoder.finish()]); + + const expColor = { R: 0.6, G: 0.6, B: 0.6, A: 0.6 }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [1, 1, 1], + { expTexelView }, + { + maxDiffULPsForNormFormat: 1, + maxDiffULPsForFloatFormat: 1, + } + ); + t.expectOK(result); + }); + +g.test('blend_constant,initial') + .desc(`Test that the blend constant is set to [0,0,0,0] at the beginning of a pass.`) + .fn(async t => { + const format = 'rgba8unorm'; + const kSize = 1; + const kWhiteColorData = new Float32Array([255, 255, 255, 255]); + + const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' } as const; + const testPipeline = t.createRenderPipelineForTest({ + format, + blend: { color: blendComponent, alpha: blendComponent }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [kSize, kSize], + format, + }); + + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData) + ); + renderPass.draw(3); + // Draw [1,1,1,1] with `src * constant + dst * 1`. + // The blend constant defaults to [0,0,0,0], so the result is + // `[1,1,1,1] * [0,0,0,0] + [0,0,0,0] * 1` = [0,0,0,0]. + renderPass.end(); + t.device.queue.submit([commandEncoder.finish()]); + + // Check that the initial blend constant is black(0,0,0,0) after setting testPipeline which has + // a white color buffer data. + const expColor = { R: 0, G: 0, B: 0, A: 0 }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [kSize, kSize], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + t.expectOK(result); + }); + +g.test('blend_constant,setting') + .desc(`Test that setting the blend constant to the RGBA values works at the beginning of a pass.`) + .paramsSubcasesOnly([ + { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, + { r: 0.5, g: 1.0, b: 0.5, a: 0.0 }, + { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }, + ]) + .fn(async t => { + const { r, g, b, a } = t.params; + + const format = 'rgba8unorm'; + const kSize = 1; + const kWhiteColorData = new Float32Array([255, 255, 255, 255]); + + const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' } as const; + const testPipeline = t.createRenderPipelineForTest({ + format, + blend: { color: blendComponent, alpha: blendComponent }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [kSize, kSize], + format, + }); + + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBlendConstant({ r, g, b, a }); + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData) + ); + renderPass.draw(3); + // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant to [r,g,b,a], so the + // result is `[1,1,1,1] * [r,g,b,a] + [0,0,0,0] * 1` = [r,g,b,a]. + renderPass.end(); + t.device.queue.submit([commandEncoder.finish()]); + + // Check that the blend constant is the same as the given constant after setting the constant + // via setBlendConstant. + const expColor = { R: r, G: g, B: b, A: a }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [kSize, kSize], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + t.expectOK(result); + }); + +g.test('blend_constant,not_inherited') + .desc(`Test that the blending constant is not inherited between render passes.`) + .fn(async t => { + const format = 'rgba8unorm'; + const kSize = 1; + const kWhiteColorData = new Float32Array([255, 255, 255, 255]); + + const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' } as const; + const testPipeline = t.createRenderPipelineForTest({ + format, + blend: { color: blendComponent, alpha: blendComponent }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [kSize, kSize], + format, + }); + + const commandEncoder = t.device.createCommandEncoder(); + { + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBlendConstant({ r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Set to white color. + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData) + ); + renderPass.draw(3); + // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant to [1,1,1,1], so the + // result is `[1,1,1,1] * [1,1,1,1] + [0,0,0,0] * 1` = [1,1,1,1]. + renderPass.end(); + } + { + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData) + ); + renderPass.draw(3); + // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant defaults to [0,0,0,0], + // so the result is `[1,1,1,1] * [0,0,0,0] + [0,0,0,0] * 1` = [0,0,0,0]. + renderPass.end(); + } + t.device.queue.submit([commandEncoder.finish()]); + + // Check that the blend constant is not inherited from the first render pass. + const expColor = { R: 0, G: 0, B: 0, A: 0 }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [kSize, kSize], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + t.expectOK(result); + }); + +const kColorWriteCombinations: readonly GPUColorWriteFlags[] = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, +]; + +g.test('color_write_mask,channel_work') + .desc( + ` + Test that the color write mask works with the zero channel, a single channel, multiple channels, + and all channels. + ` + ) + .params(u => + u // + .combine('mask', kColorWriteCombinations) + ) + .fn(async t => { + const { mask } = t.params; + + const format = 'rgba8unorm'; + const kSize = 1; + + let r = 0, + g = 0, + b = 0, + a = 0; + if (mask & GPUConst.ColorWrite.RED) { + r = 1; + } + if (mask & GPUConst.ColorWrite.GREEN) { + g = 1; + } + if (mask & GPUConst.ColorWrite.BLUE) { + b = 1; + } + if (mask & GPUConst.ColorWrite.ALPHA) { + a = 1; + } + + const testPipeline = t.createRenderPipelineForTest({ + format, + writeMask: mask, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [kSize, kSize], + format, + }); + + const kBaseColorData = new Float32Array([32, 64, 128, 192]); + + const commandEncoder = t.device.createCommandEncoder(); + { + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kBaseColorData) + ); + renderPass.draw(3); + renderPass.end(); + } + t.device.queue.submit([commandEncoder.finish()]); + + const expColor = { R: r, G: g, B: b, A: a }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [kSize, kSize], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + t.expectOK(result); + }); + +g.test('color_write_mask,blending_disabled') + .desc( + `Test that the color write mask works when blending is disabled or set to the defaults + (which has the same blending result).` + ) + .params(u => u.combine('disabled', [false, true])) + .fn(async t => { + const format = 'rgba8unorm'; + const kSize = 1; + + const blend = t.params.disabled ? undefined : { color: {}, alpha: {} }; + + const testPipeline = t.createRenderPipelineForTest({ + format, + blend, + writeMask: GPUColorWrite.RED, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [kSize, kSize], + format, + }); + + const kBaseColorData = new Float32Array([32, 64, 128, 192]); + + const commandEncoder = t.device.createCommandEncoder(); + { + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(testPipeline); + renderPass.setBindGroup( + 0, + t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kBaseColorData) + ); + // Draw [1,1,1,1] with `src * 1 + dst * 0`. So the + // result is `[1,1,1,1] * [1,1,1,1] + [0,0,0,0] * 0` = [1,1,1,1]. + renderPass.draw(3); + renderPass.end(); + } + t.device.queue.submit([commandEncoder.finish()]); + + const expColor = { R: 1, G: 0, B: 0, A: 0 }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [kSize, kSize], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + t.expectOK(result); + }); + +g.test('blending,clamping') + .desc( + ` + Test that clamping occurs at the correct points in the blend process: src value, src factor, dst + factor, and output. + - TODO: Need to test snorm formats. + - TODO: Need to test src value, srcFactor and dstFactor. + ` + ) + .params(u => + u // + .combine('format', ['rgba8unorm', 'rg16float'] as const) + .combine('srcValue', [0.4, 0.6, 0.8, 1.0]) + .combine('dstValue', [0.2, 0.4]) + ) + .fn(async t => { + const { format, srcValue, dstValue } = t.params; + + const blendComponent = { srcFactor: 'one', dstFactor: 'one', operation: 'add' } as const; + + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + fragment: { + targets: [ + { + format, + blend: { + color: blendComponent, + alpha: blendComponent, + }, + }, + ], + module: t.device.createShaderModule({ + code: ` +@fragment fn main() -> @location(0) vec4<f32> { + return vec4<f32>(${srcValue}, ${srcValue}, ${srcValue}, ${srcValue}); +} + `, + }), + entryPoint: 'main', + }, + vertex: { + module: t.device.createShaderModule({ + code: ` +@vertex fn main() -> @builtin(position) vec4<f32> { + return vec4<f32>(0.0, 0.0, 0.0, 1.0); +} + `, + }), + entryPoint: 'main', + }, + primitive: { + topology: 'point-list', + }, + }); + + const renderTarget = t.device.createTexture({ + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [1, 1, 1], + format, + }); + + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + clearValue: { r: dstValue, g: dstValue, b: dstValue, a: dstValue }, + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(pipeline); + renderPass.draw(1); + renderPass.end(); + t.device.queue.submit([commandEncoder.finish()]); + + let expValue: number; + switch (format) { + case 'rgba8unorm': // unorm types should clamp if the sum of srcValue and dstValue exceeds 1. + expValue = clamp(srcValue + dstValue, { min: 0, max: 1 }); + break; + case 'rg16float': // float format types doesn't clamp. + expValue = srcValue + dstValue; + break; + } + + const expColor = { R: expValue, G: expValue, B: expValue, A: expValue }; + const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor); + + const result = await textureContentIsOKByT2B( + t, + { texture: renderTarget }, + [1, 1, 1], + { expTexelView }, + { + maxDiffULPsForNormFormat: 1, + maxDiffULPsForFloatFormat: 1, + } + ); + t.expectOK(result); + }); |