export const description = `
- Test pipeline outputs with different color attachment number, formats, component counts, etc.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { range } from '../../../../common/util/util.js';
import {
computeBytesPerSampleFromFormats,
getColorRenderByteCost,
getTextureFormatType,
isSintOrUintFormat,
kPossibleColorRenderableTextureFormats,
} from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
import { getFragmentShaderCodeWithOutput, getPlainTypeInfo } from '../../../util/shader.js';
import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
const kVertexShader = `
@vertex fn main(
@builtin(vertex_index) VertexIndex : u32
) -> @builtin(position) vec4<f32> {
var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -3.0),
vec2<f32>(3.0, 1.0),
vec2<f32>(-1.0, 1.0));
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}
`;
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
const attachmentsIntWriteValues = [
{ R: 1, G: 2, B: 3, A: 1 },
{ R: 5, G: 6, B: 7, A: 2 },
{ R: 9, G: 10, B: 11, A: 3 },
{ R: 13, G: 14, B: 15, A: 0 },
];
const attachmentsFloatWriteValues = [
{ R: 0.12, G: 0.34, B: 0.56, A: 0 },
{ R: 0.78, G: 0.9, B: 0.19, A: 1 },
{ R: 0.28, G: 0.37, B: 0.46, A: 0.3 },
{ R: 0.55, G: 0.64, B: 0.73, A: 1 },
];
g.test('color,attachments')
.desc(`Test that pipeline with sparse color attachments write values correctly.`)
.params(u =>
u
.combine('format', kPossibleColorRenderableTextureFormats)
.beginSubcases()
.combine('attachmentCount', [2, 3, 4])
.expand('emptyAttachmentId', p => range(p.attachmentCount, i => i))
)
.fn(t => {
const { format, attachmentCount, emptyAttachmentId } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatNotUsableAsRenderAttachment(format);
const componentCount = kTexelRepresentationInfo[format].componentOrder.length;
const pixelByteCost = getColorRenderByteCost(format);
t.skipIf(
pixelByteCost === undefined ||
computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) >
t.device.limits.maxColorAttachmentBytesPerSample
);
const writeValues = isSintOrUintFormat(format)
? attachmentsIntWriteValues
: attachmentsFloatWriteValues;
const renderTargets = range(attachmentCount, () =>
t.createTextureTracked({
format,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
})
);
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kVertexShader,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: getFragmentShaderCodeWithOutput(
range(attachmentCount, i =>
i === emptyAttachmentId
? null
: {
values: [
writeValues[i].R,
writeValues[i].G,
writeValues[i].B,
writeValues[i].A,
],
plainType: getPlainTypeInfo(getTextureFormatType(format)!),
componentCount,
}
)
),
}),
entryPoint: 'main',
targets: range(attachmentCount, i => (i === emptyAttachmentId ? null : { format })),
},
primitive: { topology: 'triangle-list' },
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: range(attachmentCount, i =>
i === emptyAttachmentId
? null
: {
view: renderTargets[i].createView(),
clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 0.5 },
loadOp: 'clear',
storeOp: 'store',
}
),
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
t.device.queue.submit([encoder.finish()]);
for (let i = 0; i < attachmentCount; i++) {
if (i === emptyAttachmentId) {
continue;
}
ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: renderTargets[i] }, [
{ coord: { x: 0, y: 0 }, exp: writeValues[i] },
]);
}
});
g.test('color,component_count')
.desc(
`Test that extra components of the output (e.g. f32, vec2<f32>, vec3<f32>, vec4<f32>) are discarded.`
)
.params(u =>
u
.combine('format', kPossibleColorRenderableTextureFormats)
.beginSubcases()
.combine('componentCount', [1, 2, 3, 4])
.filter(x => x.componentCount >= kTexelRepresentationInfo[x.format].componentOrder.length)
)
.fn(t => {
const { format, componentCount } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatNotUsableAsRenderAttachment(format);
const values = [0, 1, 0, 1];
const renderTarget = t.createTextureTracked({
format,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kVertexShader,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: getFragmentShaderCodeWithOutput([
{
values,
plainType: getPlainTypeInfo(getTextureFormatType(format)!),
componentCount,
},
]),
}),
entryPoint: 'main',
targets: [{ format }],
},
primitive: { topology: 'triangle-list' },
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
t.device.queue.submit([encoder.finish()]);
t.expectSingleColor(renderTarget, format, {
size: [1, 1, 1],
exp: { R: values[0], G: values[1], B: values[2], A: values[3] },
});
});
g.test('color,component_count,blend')
.desc(
`Test that blending behaves correctly when:
- fragment output has no alpha, but the src alpha is not used for the blend operation indicated by blend factors
- attachment format has no alpha, and the dst alpha should be assumed as 1
The attachment has a load value of [1, 0, 0, 1]
`
)
.params(u =>
u
.combine('format', ['r8unorm', 'rg8unorm', 'rgba8unorm', 'bgra8unorm'] as const)
.beginSubcases()
.combineWithParams([
{
_result: [0, 0, 0, 0],
output: [0],
colorSrcFactor: 'one',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [0, 0, 0, 0],
output: [0],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [1, 0, 0, 0],
output: [0],
colorSrcFactor: 'one-minus-dst-alpha',
colorDstFactor: 'dst-alpha',
alphaSrcFactor: 'zero',
alphaDstFactor: 'one',
},
{
_result: [0.498, 0, 0, 0],
output: [0.498],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'one',
},
{
_result: [0, 1, 0, 0],
output: [0, 1],
colorSrcFactor: 'one',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [0, 1, 0, 0],
output: [0, 1],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [1, 0, 0, 0],
output: [0, 1],
colorSrcFactor: 'one-minus-dst-alpha',
colorDstFactor: 'dst-alpha',
alphaSrcFactor: 'zero',
alphaDstFactor: 'one',
},
{
_result: [0, 1, 0, 0],
output: [0, 1, 0],
colorSrcFactor: 'one',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [0, 1, 0, 0],
output: [0, 1, 0],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'zero',
},
{
_result: [1, 0, 0, 0],
output: [0, 1, 0],
colorSrcFactor: 'one-minus-dst-alpha',
colorDstFactor: 'dst-alpha',
alphaSrcFactor: 'zero',
alphaDstFactor: 'one',
},
{
_result: [0.502, 1, 0, 0.498],
output: [0, 1, 0, 0.498],
colorSrcFactor: 'one',
colorDstFactor: 'one-minus-src-alpha',
alphaSrcFactor: 'one',
alphaDstFactor: 'zero',
},
{
_result: [0.502, 0.498, 0, 0.498],
output: [0, 1, 0, 0.498],
colorSrcFactor: 'src-alpha',
colorDstFactor: 'one-minus-src-alpha',
alphaSrcFactor: 'one',
alphaDstFactor: 'zero',
},
{
_result: [0, 1, 0, 0.498],
output: [0, 1, 0, 0.498],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'one',
alphaDstFactor: 'zero',
},
{
_result: [0, 1, 0, 0.498],
output: [0, 1, 0, 0.498],
colorSrcFactor: 'dst-alpha',
colorDstFactor: 'zero',
alphaSrcFactor: 'zero',
alphaDstFactor: 'src',
},
{
_result: [1, 0, 0, 1],
output: [0, 1, 0, 0.498],
colorSrcFactor: 'one-minus-dst-alpha',
colorDstFactor: 'dst-alpha',
alphaSrcFactor: 'zero',
alphaDstFactor: 'dst-alpha',
},
] as const)
.filter(x => x.output.length >= kTexelRepresentationInfo[x.format].componentOrder.length)
)
.fn(t => {
const {
format,
_result,
output,
colorSrcFactor,
colorDstFactor,
alphaSrcFactor,
alphaDstFactor,
} = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatNotBlendable(format);
const componentCount = output.length;
const renderTarget = t.createTextureTracked({
format,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kVertexShader,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: getFragmentShaderCodeWithOutput([
{
values: output,
plainType: getPlainTypeInfo(getTextureFormatType(format)!),
componentCount,
},
]),
}),
entryPoint: 'main',
targets: [
{
format,
blend: {
color: {
srcFactor: colorSrcFactor,
dstFactor: colorDstFactor,
operation: 'add',
},
alpha: {
srcFactor: alphaSrcFactor,
dstFactor: alphaDstFactor,
operation: 'add',
},
},
},
],
},
primitive: { topology: 'triangle-list' },
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
t.device.queue.submit([encoder.finish()]);
t.expectSingleColor(renderTarget, format, {
size: [1, 1, 1],
exp: { R: _result[0], G: _result[1], B: _result[2], A: _result[3] },
});
});