export const description = `API Operation Tests for RenderPass StoreOp.
Test Coverage:
- Tests that color and depth-stencil store operations {'discard', 'store'} work correctly for a
render pass with both a color attachment and depth-stencil attachment.
TODO: use depth24plus-stencil8
- Tests that store operations {'discard', 'store'} work correctly for a render pass with multiple
color attachments.
TODO: test with more interesting loadOp values
- Tests that store operations {'discard', 'store'} work correctly for a render pass with a color
attachment for:
- All renderable color formats
- mip level set to {'0', mip > '0'}
- array layer set to {'0', layer > '1'} for 2D textures
TODO: depth slice set to {'0', slice > '0'} for 3D textures
- Tests that store operations {'discard', 'store'} work correctly for a render pass with a
depth-stencil attachment for:
- All renderable depth-stencil formats
- mip level set to {'0', mip > '0'}
- array layer set to {'0', layer > '1'} for 2D textures
TODO: test depth24plus and depth24plus-stencil8 formats
TODO: test that depth and stencil aspects are set separately
TODO: depth slice set to {'0', slice > '0'} for 3D textures
TODO: test with more interesting loadOp values`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert } from '../../../../common/util/util.js';
import {
EncodableTextureFormat,
isDepthTextureFormat,
isSintOrUintFormat,
isStencilTextureFormat,
kPossibleColorRenderableTextureFormats,
kSizedDepthStencilFormats,
} from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
import {
kTexelRepresentationInfo,
PerTexelComponent,
TexelComponent,
} from '../../../util/texture/texel_data.js';
const kMipLevel: number[] = [0, 1];
const kMipLevelCount = 2;
type NumColorAttachments = 1 | 2 | 3 | 4;
const kNumColorAttachments: NumColorAttachments[] = [1, 2, 3, 4];
const kArrayLayers: number[] = [0, 1];
const kStoreOps: GPUStoreOp[] = ['discard', 'store'];
const kHeight = 2;
const kWidth = 2;
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
g.test('render_pass_store_op,color_attachment_with_depth_stencil_attachment')
.params(u =>
u
.combine('colorStoreOperation', kStoreOps)
.combine('depthStencilStoreOperation', kStoreOps)
)
.fn(t => {
const kColorFormat: GPUTextureFormat = 'rgba8unorm';
const colorAttachment = t.createTextureTracked({
format: kColorFormat,
size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const kDepthStencilFormat: GPUTextureFormat = 'depth32float';
const depthStencilAttachment = t.createTextureTracked({
format: kDepthStencilFormat,
size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachmentView,
clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
loadOp: 'clear',
storeOp: t.params.colorStoreOperation,
},
],
depthStencilAttachment: {
view: depthStencilAttachment.createView(),
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: t.params.depthStencilStoreOperation,
},
});
pass.end();
t.device.queue.submit([encoder.finish()]);
let expectedColorValue: PerTexelComponent<number> = {};
if (t.params.colorStoreOperation === 'discard') {
expectedColorValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
} else if (t.params.colorStoreOperation === 'store') {
expectedColorValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
}
t.expectSingleColor(colorAttachment, kColorFormat, {
size: [kHeight, kWidth, 1],
exp: expectedColorValue,
});
let expectedDepthValue: PerTexelComponent<number> = {};
if (t.params.depthStencilStoreOperation === 'discard') {
expectedDepthValue = { Depth: 0.0 };
} else if (t.params.depthStencilStoreOperation === 'store') {
expectedDepthValue = { Depth: 1.0 };
}
t.expectSingleColor(depthStencilAttachment, kDepthStencilFormat, {
size: [kHeight, kWidth, 1],
exp: expectedDepthValue,
layout: { mipLevel: 0, aspect: 'depth-only' },
});
});
g.test('render_pass_store_op,color_attachment_only')
.params(u =>
u
.combine('colorFormat', kPossibleColorRenderableTextureFormats)
.combine('storeOperation', kStoreOps)
.beginSubcases()
.combine('mipLevel', kMipLevel)
.combine('arrayLayer', kArrayLayers)
)
.fn(t => {
const { colorFormat, storeOperation, mipLevel, arrayLayer } = t.params;
t.skipIfTextureFormatNotSupported(colorFormat);
t.skipIfTextureFormatNotUsableAsRenderAttachment(colorFormat);
const colorAttachment = t.createTextureTracked({
format: colorFormat,
size: { width: kWidth, height: kHeight, depthOrArrayLayers: arrayLayer + 1 },
mipLevelCount: kMipLevelCount,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const colorViewDesc: GPUTextureViewDescriptor = {
baseArrayLayer: arrayLayer,
baseMipLevel: mipLevel,
mipLevelCount: 1,
arrayLayerCount: 1,
};
const colorAttachmentView = colorAttachment.createView(colorViewDesc);
const components = new Set(
kTexelRepresentationInfo[colorFormat as EncodableTextureFormat]?.componentOrder ?? []
);
assert(components.size > 0);
const missingValue = { R: 0, G: 0, B: 0, A: 1 };
const [baseValue, maxDiff] = isSintOrUintFormat(colorFormat)
? [{ R: 12, G: 34, B: 56, A: 3 }, 0]
: [{ R: 0.8, G: 0.75, B: 0.5, A: 1.0 }, 2 / 255];
const kRGBAComponents = [
TexelComponent.R,
TexelComponent.G,
TexelComponent.B,
TexelComponent.A,
] as const;
const clearValueAsComponents = Object.fromEntries(
kRGBAComponents.map(component => [
component,
components.has(component) ? baseValue[component] : missingValue[component],
])
);
const clearValue = Object.fromEntries(
Object.entries(clearValueAsComponents).map(([k, v]) => [k.toLowerCase(), v])
) as unknown as GPUColorDict;
t.debug(`clearValue: ${JSON.stringify(clearValue)}`);
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachmentView,
clearValue,
loadOp: 'clear',
storeOp: storeOperation,
},
],
});
pass.end();
t.device.queue.submit([encoder.finish()]);
let expectedValue: PerTexelComponent<number> = {};
if (storeOperation === 'discard') {
expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
} else if (storeOperation === 'store') {
expectedValue = clearValueAsComponents;
}
ttu.expectSingleColorWithTolerance(t, colorAttachment, colorFormat, {
size: [kHeight, kWidth, 1],
slice: arrayLayer,
exp: expectedValue,
layout: { mipLevel },
maxFractionalDiff: maxDiff,
});
});
g.test('render_pass_store_op,multiple_color_attachments')
.params(u =>
u
.combine('storeOperation1', kStoreOps)
.combine('storeOperation2', kStoreOps)
.beginSubcases()
.combine('colorAttachments', kNumColorAttachments)
)
.fn(t => {
const kColorFormat: GPUTextureFormat = 'rgba8unorm';
const colorAttachments: GPUTexture[] = [];
for (let i = 0; i < t.params.colorAttachments; i++) {
colorAttachments.push(
t.createTextureTracked({
format: kColorFormat,
size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
})
);
}
const renderPassColorAttachments: GPURenderPassColorAttachment[] = [];
for (let i = 0; i < t.params.colorAttachments; i++) {
renderPassColorAttachments.push({
view: colorAttachments[i].createView(),
clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
loadOp: 'clear',
storeOp: i % 2 === 0 ? t.params.storeOperation1 : t.params.storeOperation2,
});
}
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: renderPassColorAttachments,
});
pass.end();
t.device.queue.submit([encoder.finish()]);
let expectedValue: PerTexelComponent<number> = {};
for (let i = 0; i < t.params.colorAttachments; i++) {
if (renderPassColorAttachments[i].storeOp === 'discard') {
expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
} else if (renderPassColorAttachments[i].storeOp === 'store') {
expectedValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
}
t.expectSingleColor(colorAttachments[i], kColorFormat, {
size: [kHeight, kWidth, 1],
exp: expectedValue,
});
}
});
g.test('render_pass_store_op,depth_stencil_attachment_only')
.desc(
`
Tests that render pass depth stencil store operations work correctly for all renderable color
formats, mip levels and array layers.
- x= all (sized) depth stencil formats, all store ops, multiple mip levels, multiple array layers
TODO: Also test unsized depth/stencil formats [1]
`
)
.params(u =>
u
.combine('depthStencilFormat', kSizedDepthStencilFormats)
.combine('storeOperation', kStoreOps)
.beginSubcases()
.combine('mipLevel', kMipLevel)
.combine('arrayLayer', kArrayLayers)
)
.fn(t => {
const depthStencilTexture = t.createTextureTracked({
format: t.params.depthStencilFormat,
size: { width: kWidth, height: kHeight, depthOrArrayLayers: t.params.arrayLayer + 1 },
mipLevelCount: kMipLevelCount,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const depthStencilViewDesc: GPUTextureViewDescriptor = {
baseArrayLayer: t.params.arrayLayer,
baseMipLevel: t.params.mipLevel,
mipLevelCount: 1,
arrayLayerCount: 1,
};
const depthStencilAttachmentView = depthStencilTexture.createView(depthStencilViewDesc);
const encoder = t.device.createCommandEncoder();
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: depthStencilAttachmentView,
};
if (isDepthTextureFormat(t.params.depthStencilFormat)) {
depthStencilAttachment.depthClearValue = 1.0;
depthStencilAttachment.depthLoadOp = 'clear';
depthStencilAttachment.depthStoreOp = t.params.storeOperation;
}
if (isStencilTextureFormat(t.params.depthStencilFormat)) {
depthStencilAttachment.stencilClearValue = 1;
depthStencilAttachment.stencilLoadOp = 'clear';
depthStencilAttachment.stencilStoreOp = t.params.storeOperation;
}
const pass = encoder.beginRenderPass({
colorAttachments: [],
depthStencilAttachment,
});
pass.end();
t.device.queue.submit([encoder.finish()]);
let expectedDepthValue: PerTexelComponent<number> = {};
let expectedStencilValue: PerTexelComponent<number> = {};
if (t.params.storeOperation === 'discard') {
expectedDepthValue = { Depth: 0.0 };
expectedStencilValue = { Stencil: 0 };
} else if (t.params.storeOperation === 'store') {
expectedDepthValue = { Depth: 1.0 };
expectedStencilValue = { Stencil: 1 };
}
if (isDepthTextureFormat(t.params.depthStencilFormat)) {
t.expectSingleColor(depthStencilTexture, t.params.depthStencilFormat, {
size: [kHeight, kWidth, 1],
slice: t.params.arrayLayer,
exp: expectedDepthValue,
layout: { mipLevel: t.params.mipLevel, aspect: 'depth-only' },
});
}
if (isStencilTextureFormat(t.params.depthStencilFormat)) {
t.expectSingleColor(depthStencilTexture, t.params.depthStencilFormat, {
size: [kHeight, kWidth, 1],
slice: t.params.arrayLayer,
exp: expectedStencilValue,
layout: { mipLevel: t.params.mipLevel, aspect: 'stencil-only' },
});
}
});