export const description = `
Test related to depth buffer, depth op, compare func, etc.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { TypedArrayBufferView } from '../../../../common/util/util.js';
import { isStencilTextureFormat, kDepthTextureFormats } from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
import { TexelView } from '../../../util/texture/texel_view.js';
const backgroundColor = [0x00, 0x00, 0x00, 0xff];
const triangleColor = [0xff, 0xff, 0xff, 0xff];
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;
depth: number;
};
class DepthTest extends AllFeaturesMaxLimitsGPUTest {
runDepthStateTest(testStates: TestStates[], expectedColor: Float32Array) {
const renderTargetFormat = 'rgba8unorm';
const renderTarget = this.createTextureTracked({
format: renderTargetFormat,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const depthStencilFormat: GPUTextureFormat = 'depth24plus-stencil8';
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 depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: depthTexture.createView(),
depthLoadOp: 'load',
depthStoreOp: 'store',
stencilLoadOp: 'load',
stencilStoreOp: 'store',
};
const encoder = this.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
loadOp: 'load',
storeOp: 'store',
},
],
depthStencilAttachment,
});
for (const test of testStates) {
const testPipeline = this.createRenderPipelineForTest(test.state, test.depth);
pass.setPipeline(testPipeline);
pass.setBindGroup(
0,
this.createBindGroupForTest(testPipeline.getBindGroupLayout(0), test.color)
);
pass.draw(1);
}
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,
depth: number
): 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, ${depth}, 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(DepthTest);
g.test('depth_disabled')
.desc('Tests render results with depth test disabled.')
.fn(t => {
const depthSpencilFormat: GPUTextureFormat = 'depth24plus-stencil8';
const state = {
format: depthSpencilFormat,
depthWriteEnabled: false,
depthCompare: 'always' as GPUCompareFunction,
};
const testStates = [
{ state, color: kBaseColor, depth: 0.0 },
{ state, color: kRedStencilColor, depth: 0.5 },
{ state, color: kGreenStencilColor, depth: 1.0 },
];
for (let last = 0; last < 3; ++last) {
const i = (last + 1) % 3;
const j = (last + 2) % 3;
t.runDepthStateTest([testStates[i], testStates[j], testStates[last]], testStates[last].color);
t.runDepthStateTest([testStates[j], testStates[i], testStates[last]], testStates[last].color);
}
});
g.test('depth_write_disabled')
.desc(
`
Test that depthWriteEnabled behaves as expected.
If enabled, a depth value of 0.0 is written.
If disabled, it's not written, so it keeps the previous value of 1.0.
Use a depthCompare: 'equal' check at the end to check the value.
`
)
.params(u =>
u
.combineWithParams([
{ depthWriteEnabled: false, lastDepth: 0.0, _expectedColor: kRedStencilColor },
{ depthWriteEnabled: true, lastDepth: 0.0, _expectedColor: kGreenStencilColor },
{ depthWriteEnabled: false, lastDepth: 1.0, _expectedColor: kGreenStencilColor },
{ depthWriteEnabled: true, lastDepth: 1.0, _expectedColor: kRedStencilColor },
])
)
.fn(t => {
const { depthWriteEnabled, lastDepth, _expectedColor } = t.params;
const depthStencilFormat: GPUTextureFormat = 'depth24plus-stencil8';
const stencilState = {
compare: 'always',
failOp: 'keep',
depthFailOp: 'keep',
passOp: 'keep',
} as const;
const baseState = {
format: depthStencilFormat,
depthWriteEnabled: true,
depthCompare: 'always',
stencilFront: stencilState,
stencilBack: stencilState,
stencilReadMask: 0xff,
stencilWriteMask: 0xff,
} as const;
const depthWriteState = {
format: depthStencilFormat,
depthWriteEnabled,
depthCompare: 'always',
stencilFront: stencilState,
stencilBack: stencilState,
stencilReadMask: 0xff,
stencilWriteMask: 0xff,
} as const;
const checkState = {
format: depthStencilFormat,
depthWriteEnabled: false,
depthCompare: 'equal',
stencilFront: stencilState,
stencilBack: stencilState,
stencilReadMask: 0xff,
stencilWriteMask: 0xff,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, depth: 1.0 },
{ state: depthWriteState, color: kRedStencilColor, depth: 0.0 },
{ state: checkState, color: kGreenStencilColor, depth: lastDepth },
];
t.runDepthStateTest(testStates, _expectedColor);
});
g.test('depth_test_fail')
.desc(
`
Test that render results on depth test failure cases with 'less' depthCompare operation and
depthWriteEnabled is true.
`
)
.params(u =>
u
.combineWithParams([
{ secondDepth: 1.0, lastDepth: 2.0, _expectedColor: kBaseColor },
{ secondDepth: 0.0, lastDepth: 2.0, _expectedColor: kRedStencilColor },
{ secondDepth: 2.0, lastDepth: 0.9, _expectedColor: kGreenStencilColor },
] as const)
)
.fn(t => {
const { secondDepth, lastDepth, _expectedColor } = t.params;
const depthStencilFormat: GPUTextureFormat = 'depth24plus-stencil8';
const baseState = {
format: depthStencilFormat,
depthWriteEnabled: true,
depthCompare: 'always',
stencilReadMask: 0xff,
stencilWriteMask: 0xff,
} as const;
const depthTestState = {
format: depthStencilFormat,
depthWriteEnabled: true,
depthCompare: 'less',
stencilReadMask: 0xff,
stencilWriteMask: 0xff,
} as const;
const testStates = [
{ state: baseState, color: kBaseColor, depth: 1.0 },
{ state: depthTestState, color: kRedStencilColor, depth: secondDepth },
{ state: depthTestState, color: kGreenStencilColor, depth: lastDepth },
];
t.runDepthStateTest(testStates, _expectedColor);
});
const kMiddleDepthValue = 0.4;
g.test('depth_compare_func')
.desc(
`Tests each depth compare function works properly. Clears the depth attachment to various values, and renders a point at depth 0.5 with various depthCompare modes.`
)
.params(u =>
u.combine('format', kDepthTextureFormats).combineWithParams([
{ depthCompare: 'never', depthClearValue: 1.0, _expected: backgroundColor },
{ depthCompare: 'never', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
{ depthCompare: 'never', depthClearValue: 0.0, _expected: backgroundColor },
{ depthCompare: 'less', depthClearValue: 1.0, _expected: triangleColor },
{ depthCompare: 'less', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
{ depthCompare: 'less', depthClearValue: 0.0, _expected: backgroundColor },
{ depthCompare: 'less-equal', depthClearValue: 1.0, _expected: triangleColor },
{
depthCompare: 'less-equal',
depthClearValue: kMiddleDepthValue,
_expected: triangleColor,
},
{ depthCompare: 'less-equal', depthClearValue: 0.0, _expected: backgroundColor },
{ depthCompare: 'equal', depthClearValue: 1.0, _expected: backgroundColor },
{ depthCompare: 'equal', depthClearValue: kMiddleDepthValue, _expected: triangleColor },
{ depthCompare: 'equal', depthClearValue: 0.0, _expected: backgroundColor },
{ depthCompare: 'not-equal', depthClearValue: 1.0, _expected: triangleColor },
{
depthCompare: 'not-equal',
depthClearValue: kMiddleDepthValue,
_expected: backgroundColor,
},
{ depthCompare: 'not-equal', depthClearValue: 0.0, _expected: triangleColor },
{ depthCompare: 'greater-equal', depthClearValue: 1.0, _expected: backgroundColor },
{
depthCompare: 'greater-equal',
depthClearValue: kMiddleDepthValue,
_expected: triangleColor,
},
{ depthCompare: 'greater-equal', depthClearValue: 0.0, _expected: triangleColor },
{ depthCompare: 'greater', depthClearValue: 1.0, _expected: backgroundColor },
{ depthCompare: 'greater', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
{ depthCompare: 'greater', depthClearValue: 0.0, _expected: triangleColor },
{ depthCompare: 'always', depthClearValue: 1.0, _expected: triangleColor },
{ depthCompare: 'always', depthClearValue: kMiddleDepthValue, _expected: triangleColor },
{ depthCompare: 'always', depthClearValue: 0.0, _expected: triangleColor },
] as const)
)
.fn(t => {
const { depthCompare, depthClearValue, _expected, format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const colorAttachmentFormat = 'rgba8unorm';
const colorAttachment = t.createTextureTracked({
format: colorAttachmentFormat,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const depthTexture = t.createTextureTracked({
size: { width: 1, height: 1 },
format,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
const depthTextureView = depthTexture.createView();
const pipelineDescriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: `
@vertex fn main(
@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
return vec4<f32>(0.5, 0.5, ${kMiddleDepthValue}, 1.0);
}
`,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: `
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}
`,
}),
entryPoint: 'main',
targets: [{ format: colorAttachmentFormat }],
},
primitive: { topology: 'point-list' },
depthStencil: {
depthWriteEnabled: true,
depthCompare,
format,
},
};
const pipeline = t.device.createRenderPipeline(pipelineDescriptor);
const encoder = t.device.createCommandEncoder();
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: depthTextureView,
depthClearValue,
depthLoadOp: 'clear',
depthStoreOp: 'store',
};
if (isStencilTextureFormat(format)) {
depthStencilAttachment.stencilClearValue = 0;
depthStencilAttachment.stencilLoadOp = 'clear';
depthStencilAttachment.stencilStoreOp = 'store';
}
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachmentView,
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment,
});
pass.setPipeline(pipeline);
pass.draw(1);
pass.end();
t.device.queue.submit([encoder.finish()]);
ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: colorAttachment }, [
{
coord: { x: 0, y: 0 },
exp: new Uint8Array(_expected),
},
]);
});
g.test('reverse_depth')
.desc(
`Tests simple rendering with reversed depth buffer, ensures depth test works properly: fragments are in correct order and out of range fragments are clipped.
Note that in real use case the depth range remapping is done by the modified projection matrix.
(see https://developer.nvidia.com/content/depth-precision-visualized).`
)
.params(u => u.combine('reversed', [false, true]))
.fn(t => {
const colorAttachmentFormat = 'rgba8unorm';
const colorAttachment = t.createTextureTracked({
format: colorAttachmentFormat,
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const depthBufferFormat = 'depth32float';
const depthTexture = t.createTextureTracked({
size: { width: 1, height: 1 },
format: depthBufferFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
const depthTextureView = depthTexture.createView();
const pipelineDescriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: `
struct Output {
@builtin(position) Position : vec4<f32>,
@location(0) color : vec4<f32>,
};
@vertex fn main(
@builtin(vertex_index) VertexIndex : u32,
@builtin(instance_index) InstanceIndex : u32) -> Output {
let zv = array(0.2, 0.3, -0.1, 1.1);
let z = zv[InstanceIndex];
var output : Output;
output.Position = vec4<f32>(0.5, 0.5, z, 1.0);
var colors : array<vec4<f32>, 4> = array<vec4<f32>, 4>(
vec4<f32>(1.0, 0.0, 0.0, 1.0),
vec4<f32>(0.0, 1.0, 0.0, 1.0),
vec4<f32>(0.0, 0.0, 1.0, 1.0),
vec4<f32>(1.0, 1.0, 1.0, 1.0)
);
output.color = colors[InstanceIndex];
return output;
}
`,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: `
@fragment fn main(
@location(0) color : vec4<f32>
) -> @location(0) vec4<f32> {
return color;
}
`,
}),
entryPoint: 'main',
targets: [{ format: colorAttachmentFormat }],
},
primitive: { topology: 'point-list' },
depthStencil: {
depthWriteEnabled: true,
depthCompare: t.params.reversed ? 'greater' : 'less',
format: depthBufferFormat,
},
};
const pipeline = t.device.createRenderPipeline(pipelineDescriptor);
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachmentView,
clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: depthTextureView,
depthClearValue: t.params.reversed ? 0.0 : 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
});
pass.setPipeline(pipeline);
pass.draw(1, 4);
pass.end();
t.device.queue.submit([encoder.finish()]);
ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: colorAttachment }, [
{
coord: { x: 0, y: 0 },
exp: new Uint8Array(
t.params.reversed ? [0x00, 0xff, 0x00, 0xff] : [0xff, 0x00, 0x00, 0xff]
),
},
]);
});