export const description = `
Test related to stencil states, stencil op, compare func, etc.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, TypedArrayBufferView } from '../../../../common/util/util.js';
import {
kBufferSizeAlignment,
kMinDynamicBufferOffsetAlignment,
} from '../../../capability_info.js';
import {
kStencilTextureFormats,
DepthStencilFormat,
isDepthTextureFormat,
isStencilTextureFormat,
kDepthStencilFormats,
depthStencilFormatAspectSize,
} from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
import { align } from '../../../util/math.js';
import { DataArrayGenerator } from '../../../util/texture/data_generation.js';
import { dataBytesForCopyOrFail, getTextureCopyLayout } from '../../../util/texture/layout.js';
import { TexelView } from '../../../util/texture/texel_view.js';
const kBaseColor = new Float32Array([1.0, 1.0, 1.0, 1.0]);
const kRedStencilColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
const kGreenStencilColor = new Float32Array([0.0, 1.0, 0.0, 1.0]);
type TestStates = {
state: GPUDepthStencilState;
color: Float32Array;
stencil: number | undefined;
};
class StencilTest extends AllFeaturesMaxLimitsGPUTest {
checkStencilOperation(
depthStencilFormat: DepthStencilFormat,
testStencilState: GPUStencilFaceState,
initialStencil: number,
_expectedStencil: number,
depthCompare: GPUCompareFunction = 'always'
) {
const kReferenceStencil = 3;
const baseStencilState = {
compare: 'always',
failOp: 'keep',
passOp: 'replace',
} as const;
const stencilState = {
compare: 'equal',
failOp: 'keep',
passOp: 'keep',
} as const;
const baseState = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: baseStencilState,
stencilBack: baseStencilState,
} as const;
const testState = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare,
stencilFront: testStencilState,
stencilBack: testStencilState,
} as const;
const testState2 = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: stencilState,
stencilBack: stencilState,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, stencil: initialStencil },
{ state: testState, color: kRedStencilColor, stencil: kReferenceStencil },
{ state: testState2, color: kGreenStencilColor, stencil: _expectedStencil },
];
this.runStencilStateTest(depthStencilFormat, testStates, kGreenStencilColor);
}
checkStencilCompareFunction(
depthStencilFormat: DepthStencilFormat,
compareFunction: GPUCompareFunction,
stencilRefValue: number,
expectedColor: Float32Array
) {
const baseStencilState = {
compare: 'always',
failOp: 'keep',
passOp: 'replace',
} as const;
const stencilState = {
compare: compareFunction,
failOp: 'keep',
passOp: 'keep',
} as const;
const baseState = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: baseStencilState,
stencilBack: baseStencilState,
} as const;
const testState = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: stencilState,
stencilBack: stencilState,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, stencil: 1 },
{ state: testState, color: kGreenStencilColor, stencil: stencilRefValue },
];
this.runStencilStateTest(depthStencilFormat, testStates, expectedColor);
}
runStencilStateTest(
depthStencilFormat: DepthStencilFormat,
testStates: TestStates[],
expectedColor: Float32Array,
isSingleEncoderMultiplePass: boolean = false
) {
const renderTargetFormat = 'rgba8unorm';
const renderTarget = this.createTextureTracked({
format: renderTargetFormat,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const depthTexture = this.createTextureTracked({
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
format: depthStencilFormat,
sampleCount: 1,
mipLevelCount: 1,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
});
const hasDepth = isDepthTextureFormat(depthStencilFormat);
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: depthTexture.createView(),
depthLoadOp: hasDepth ? 'load' : undefined,
depthStoreOp: hasDepth ? 'store' : undefined,
stencilLoadOp: 'load',
stencilStoreOp: 'store',
};
const encoder = this.device.createCommandEncoder();
let pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
loadOp: 'load',
storeOp: 'store',
},
],
depthStencilAttachment,
});
if (isSingleEncoderMultiplePass) {
pass.end();
}
for (const test of testStates) {
if (isSingleEncoderMultiplePass) {
pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
loadOp: 'load',
storeOp: 'store',
},
],
depthStencilAttachment,
});
}
const testPipeline = this.createRenderPipelineForTest(test.state);
pass.setPipeline(testPipeline);
if (test.stencil !== undefined) {
pass.setStencilReference(test.stencil);
}
pass.setBindGroup(
0,
this.createBindGroupForTest(testPipeline.getBindGroupLayout(0), test.color)
);
pass.draw(1);
if (isSingleEncoderMultiplePass) {
pass.end();
}
}
if (!isSingleEncoderMultiplePass) {
pass.end();
}
this.device.queue.submit([encoder.finish()]);
const expColor = {
R: expectedColor[0],
G: expectedColor[1],
B: expectedColor[2],
A: expectedColor[3],
};
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, _coords => expColor);
ttu.expectTexelViewComparisonIsOkInTexture(
this,
{ texture: renderTarget },
expTexelView,
[1, 1]
);
}
createRenderPipelineForTest(depthStencil: GPUDepthStencilState): GPURenderPipeline {
return this.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: this.device.createShaderModule({
code: `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
`,
}),
entryPoint: 'main',
},
fragment: {
targets: [{ format: 'rgba8unorm' }],
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 vec4<f32>(params.color);
}`,
}),
entryPoint: 'main',
},
primitive: { topology: 'point-list' },
depthStencil,
});
}
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(StencilTest);
g.test('stencil_compare_func')
.desc(
`
Tests that stencil comparison functions with the stencil reference value works as expected.
`
)
.params(u =>
u
.combine('format', kStencilTextureFormats)
.combineWithParams([
{ stencilCompare: 'always', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'always', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'always', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'equal', stencilRefValue: 0, _expectedColor: kBaseColor },
{ stencilCompare: 'equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'equal', stencilRefValue: 2, _expectedColor: kBaseColor },
{ stencilCompare: 'greater', stencilRefValue: 0, _expectedColor: kBaseColor },
{ stencilCompare: 'greater', stencilRefValue: 1, _expectedColor: kBaseColor },
{ stencilCompare: 'greater', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'greater-equal', stencilRefValue: 0, _expectedColor: kBaseColor },
{ stencilCompare: 'greater-equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'greater-equal', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'less', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'less', stencilRefValue: 1, _expectedColor: kBaseColor },
{ stencilCompare: 'less', stencilRefValue: 2, _expectedColor: kBaseColor },
{ stencilCompare: 'less-equal', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'less-equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'less-equal', stencilRefValue: 2, _expectedColor: kBaseColor },
{ stencilCompare: 'never', stencilRefValue: 0, _expectedColor: kBaseColor },
{ stencilCompare: 'never', stencilRefValue: 1, _expectedColor: kBaseColor },
{ stencilCompare: 'never', stencilRefValue: 2, _expectedColor: kBaseColor },
{ stencilCompare: 'not-equal', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
{ stencilCompare: 'not-equal', stencilRefValue: 1, _expectedColor: kBaseColor },
{ stencilCompare: 'not-equal', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
] as const)
)
.fn(t => {
const { format, stencilCompare, stencilRefValue, _expectedColor } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.checkStencilCompareFunction(format, stencilCompare, stencilRefValue, _expectedColor);
});
g.test('stencil_passOp_operation')
.desc(
`
Test that the stencil operation is executed on stencil pass. A triangle is drawn with the 'always'
comparison function, so it should pass. Then, test that each pass stencil operation works with the
given stencil values correctly as expected. For example,
- If the pass operation is 'keep', it keeps the initial stencil value.
- If the pass operation is 'replace', it replaces the initial stencil value with the reference
stencil value.
`
)
.params(u =>
u
.combine('format', kStencilTextureFormats)
.combineWithParams([
{ passOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
{ passOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
{ passOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
{ passOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
{ passOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
{ passOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
{ passOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
{ passOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
{ passOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
{ passOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
{ passOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
{ passOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff },
] as const)
)
.fn(t => {
const { format, passOp, initialStencil, _expectedStencil } = t.params;
t.skipIfTextureFormatNotSupported(format);
const stencilState = {
compare: 'always',
failOp: 'keep',
passOp,
} as const;
t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil);
});
g.test('stencil_failOp_operation')
.desc(
`
Test that the stencil operation is executed on stencil fail. A triangle is drawn with the 'never'
comparison function, so it should fail. Then, test that each fail stencil operation works with the
given stencil values correctly as expected. For example,
- If the fail operation is 'keep', it keeps the initial stencil value.
- If the fail operation is 'replace', it replaces the initial stencil value with the reference
stencil value.
`
)
.params(u =>
u
.combine('format', kStencilTextureFormats)
.combineWithParams([
{ failOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
{ failOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
{ failOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
{ failOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
{ failOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
{ failOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
{ failOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
{ failOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
{ failOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
{ failOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
{ failOp: 'decrement-wrap', initialStencil: 2, _expectedStencil: 1 },
{ failOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
{ failOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff },
] as const)
)
.fn(t => {
const { format, failOp, initialStencil, _expectedStencil } = t.params;
t.skipIfTextureFormatNotSupported(format);
const stencilState = {
compare: 'never',
failOp,
passOp: 'keep',
} as const;
t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil);
});
g.test('stencil_depthFailOp_operation')
.desc(
`
Test that the stencil operation is executed on depthCompare fail. A triangle is drawn with the
'never' depthCompare, so it should fail the depth test. Then, test that each 'depthFailOp' stencil operation
works with the given stencil values correctly as expected. For example,
- If the depthFailOp operation is 'keep', it keeps the initial stencil value.
- If the depthFailOp operation is 'replace', it replaces the initial stencil value with the
reference stencil value.
`
)
.params(u =>
u
.combine(
'format',
kDepthStencilFormats.filter(
format => isDepthTextureFormat(format) && isStencilTextureFormat(format)
)
)
.combineWithParams([
{ depthFailOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
{ depthFailOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
{ depthFailOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
{ depthFailOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
{ depthFailOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
{ depthFailOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
{ depthFailOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
{ depthFailOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
{ depthFailOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
{ depthFailOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
{ depthFailOp: 'decrement-wrap', initialStencil: 2, _expectedStencil: 1 },
{ depthFailOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
{ depthFailOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff },
] as const)
)
.fn(t => {
const { format, depthFailOp, initialStencil, _expectedStencil } = t.params;
t.skipIfTextureFormatNotSupported(format);
const stencilState = {
compare: 'always',
failOp: 'keep',
passOp: 'keep',
depthFailOp,
} as const;
t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil, 'never');
});
g.test('stencil_read_write_mask')
.desc(
`
Tests that setting a stencil read/write masks work. Basically, The base triangle sets 3 to the
stencil, and then try to draw a triangle with different stencil values.
- In case that 'write' mask is 1,
* If the stencil of the triangle is 1, it draws because
'base stencil(3) & write mask(1) == triangle stencil(1)'.
* If the stencil of the triangle is 2, it does not draw because
'base stencil(3) & write mask(1) != triangle stencil(2)'.
- In case that 'read' mask is 2,
* If the stencil of the triangle is 1, it does not draw because
'base stencil(3) & read mask(2) != triangle stencil(1)'.
* If the stencil of the triangle is 2, it draws because
'base stencil(3) & read mask(2) == triangle stencil(2)'.
`
)
.params(u =>
u
.combine('format', kStencilTextureFormats)
.combineWithParams([
{ maskType: 'write', stencilRefValue: 1, _expectedColor: kRedStencilColor },
{ maskType: 'write', stencilRefValue: 2, _expectedColor: kBaseColor },
{ maskType: 'read', stencilRefValue: 1, _expectedColor: kBaseColor },
{ maskType: 'read', stencilRefValue: 2, _expectedColor: kRedStencilColor },
])
)
.fn(t => {
const { format, maskType, stencilRefValue, _expectedColor } = t.params;
t.skipIfTextureFormatNotSupported(format);
const baseStencilState = {
compare: 'always',
failOp: 'keep',
passOp: 'replace',
} as const;
const stencilState = {
compare: 'equal',
failOp: 'keep',
passOp: 'keep',
} as const;
const baseState = {
format,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: baseStencilState,
stencilBack: baseStencilState,
stencilReadMask: 0xff,
stencilWriteMask: maskType === 'write' ? 0x1 : 0xff,
} as const;
const testState = {
format,
depthWriteEnabled: false,
depthCompare: 'always',
stencilFront: stencilState,
stencilBack: stencilState,
stencilReadMask: maskType === 'read' ? 0x2 : 0xff,
stencilWriteMask: 0xff,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, stencil: 3 },
{ state: testState, color: kRedStencilColor, stencil: stencilRefValue },
];
t.runStencilStateTest(format, testStates, _expectedColor);
});
g.test('stencil_reference_initialized')
.desc('Test that stencil reference is initialized as zero for new render pass.')
.params(u => u.combine('format', kStencilTextureFormats))
.fn(t => {
const { format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const baseStencilState = {
compare: 'always',
passOp: 'replace',
} as const;
const testStencilState = {
compare: 'equal',
passOp: 'keep',
} as const;
const hasDepth = isDepthTextureFormat(format);
const baseState = {
format,
depthWriteEnabled: hasDepth,
depthCompare: 'always',
stencilFront: baseStencilState,
stencilBack: baseStencilState,
} as const;
const testState = {
format,
depthWriteEnabled: hasDepth,
depthCompare: 'always',
stencilFront: testStencilState,
stencilBack: testStencilState,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, stencil: 0x1 },
{ state: baseState, color: kRedStencilColor, stencil: undefined },
{ state: testState, color: kGreenStencilColor, stencil: 0x0 },
];
t.runStencilStateTest(format, testStates, kGreenStencilColor, true);
});
const dataGenerator = new DataArrayGenerator();
g.test('stencil_accumulation')
.desc(
`A variation of a technique previously used to verify stencil texture copy that was observed to
fail on some Qualcomm chipsets. Attempts to read back from a stencil texture by doing multiple
fullscreen renders, one per stencil bit. Each pass sets the stencil mask and stencil reference
to 1 << passIndex, and renders 1 << passIndex into the red channel with additive blending. This
should duplicate the stencil values into the red channel of the texture, and on most GPUs it
works. Technique is replicated here to catch driver bugs, while the original stencil copy test
has been updated to verify the stencil values using copyTextureToBuffer, which is more
straightforward and allows those tests to focus on the behavior of the APIs in question.
`
)
.params(u =>
u
.combine('format', kDepthStencilFormats)
.filter(t => isStencilTextureFormat(t.format))
.beginSubcases()
.combineWithParams([
{ offsetInBlocks: 0, dataPaddingInBytes: 0 },
{ offsetInBlocks: 16, dataPaddingInBytes: 0 },
{ offsetInBlocks: 768, dataPaddingInBytes: 0 },
{ offsetInBlocks: 0, dataPaddingInBytes: 1 },
])
.combine('copyDepth', [1, 2])
.combine('mipLevel', [0, 2])
)
.fn(t => {
const { format, offsetInBlocks, dataPaddingInBytes, copyDepth, mipLevel } = t.params;
t.skipIfTextureFormatNotSupported(format);
const aspect = 'stencil-only';
const bytesPerBlock = depthStencilFormatAspectSize(format, aspect);
const initialDataOffset = offsetInBlocks * bytesPerBlock;
const copySize = [3, 3, copyDepth] as const;
const rowsPerImage = 3;
const bytesPerRow = 256;
const textureSize = [copySize[0] << mipLevel, copySize[1] << mipLevel, copyDepth] as const;
const minDataSize = dataBytesForCopyOrFail({
layout: { offset: initialDataOffset, bytesPerRow, rowsPerImage },
format: 'stencil8',
copySize,
method: 'WriteTexture',
});
const initialDataSize = minDataSize + dataPaddingInBytes;
const srcTexture = t.createTextureTracked({
size: textureSize,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
format,
mipLevelCount: mipLevel + 1,
});
const writeSize = [textureSize[0] >> mipLevel, textureSize[1] >> mipLevel, textureSize[2]];
const initialData = dataGenerator.generateView(
align(initialDataSize, kBufferSizeAlignment),
0,
initialDataOffset
);
t.queue.writeTexture(
{ texture: srcTexture, aspect: 'stencil-only', mipLevel },
initialData,
{
offset: initialDataOffset,
bytesPerRow,
rowsPerImage,
},
writeSize
);
const stencilBitCount = 8;
const uniformBufferSize = kMinDynamicBufferOffsetAlignment * (stencilBitCount - 1) + 4;
const uniformBufferData = new Uint32Array(uniformBufferSize / 4);
for (let i = 1; i < stencilBitCount; ++i) {
uniformBufferData[(kMinDynamicBufferOffsetAlignment / 4) * i] = i;
}
const uniformBuffer = t.makeBufferWithContents(
uniformBufferData,
GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
);
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
buffer: {
type: 'uniform',
minBindingSize: 4,
hasDynamicOffset: true,
},
},
],
});
const renderPipelineDescriptorBase: GPURenderPipelineDescriptor = {
layout: t.device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
vertex: {
module: t.device.createShaderModule({
code: `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
var pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, -1.0));
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}`,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: `
struct Params {
stencilBitIndex: u32
};
@group(0) @binding(0) var<uniform> param: Params;
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(f32(1u << param.stencilBitIndex) / 255.0, 0.0, 0.0, 0.0);
}`,
}),
entryPoint: 'main',
targets: [
{
format: 'r8unorm',
blend: {
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
alpha: {},
},
},
],
},
primitive: {
topology: 'triangle-list',
},
depthStencil: {
depthWriteEnabled: false,
depthCompare: 'always',
format,
stencilFront: {
compare: 'equal',
},
stencilBack: {
compare: 'equal',
},
},
};
const bindGroup = t.device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
size: 4,
},
},
],
});
const copyFromOutputTextureLayout = getTextureCopyLayout(
format,
'2d',
[textureSize[0], textureSize[1], 1],
{
mipLevel,
aspect: 'stencil-only',
}
);
const outputTextureSize = [
copyFromOutputTextureLayout.mipSize[0],
copyFromOutputTextureLayout.mipSize[1],
1,
];
const outputTexture = t.createTextureTracked({
format: 'r8unorm',
size: outputTextureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
for (let stencilTextureLayer = 0; stencilTextureLayer < textureSize[2]; ++stencilTextureLayer) {
const encoder = t.device.createCommandEncoder();
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: srcTexture.createView({
baseMipLevel: mipLevel,
mipLevelCount: 1,
baseArrayLayer: stencilTextureLayer,
arrayLayerCount: 1,
}),
};
if (isDepthTextureFormat(format)) {
depthStencilAttachment.depthClearValue = 0;
depthStencilAttachment.depthLoadOp = 'clear';
depthStencilAttachment.depthStoreOp = 'store';
}
if (isStencilTextureFormat(format)) {
depthStencilAttachment.stencilLoadOp = 'load';
depthStencilAttachment.stencilStoreOp = 'store';
}
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
view: outputTexture.createView(),
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment,
});
for (let stencilBitIndex = 0; stencilBitIndex < stencilBitCount; ++stencilBitIndex) {
const renderPipelineDescriptor = renderPipelineDescriptorBase;
assert(renderPipelineDescriptor.depthStencil !== undefined);
renderPipelineDescriptor.depthStencil.stencilReadMask = 1 << stencilBitIndex;
const renderPipeline = t.device.createRenderPipeline(renderPipelineDescriptor);
renderPass.setPipeline(renderPipeline);
renderPass.setStencilReference(1 << stencilBitIndex);
renderPass.setBindGroup(0, bindGroup, [stencilBitIndex * kMinDynamicBufferOffsetAlignment]);
renderPass.draw(6);
}
renderPass.end();
const outputStagingBuffer = t.createBufferTracked({
size: copyFromOutputTextureLayout.byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
encoder.copyTextureToBuffer(
{
texture: outputTexture,
},
{
buffer: outputStagingBuffer,
bytesPerRow: copyFromOutputTextureLayout.bytesPerRow,
rowsPerImage: copyFromOutputTextureLayout.rowsPerImage,
},
outputTextureSize
);
t.queue.submit([encoder.finish()]);
for (let y = 0; y < copyFromOutputTextureLayout.mipSize[1]; ++y) {
const dataStart =
initialDataOffset + bytesPerRow * rowsPerImage * stencilTextureLayer + bytesPerRow * y;
t.expectGPUBufferValuesEqual(
outputStagingBuffer,
initialData.slice(dataStart, dataStart + copyFromOutputTextureLayout.mipSize[0]),
copyFromOutputTextureLayout.bytesPerRow * y
);
}
}
});