blob: 75e91334e4d50e0eb16979c890fe4547aa0c846f [file] [log] [blame]
export const description = `
Tests render results with different depth bias values like 'positive', 'negative',
'slope', 'clamp', etc.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { unreachable } from '../../../../common/util/util.js';
import {
DepthStencilFormat,
EncodableTextureFormat,
isDepthTextureFormat,
isStencilTextureFormat,
} 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';
enum QuadAngle {
Flat,
TiltedX,
}
// Floating point depth buffers use the following formula to calculate bias
// bias = depthBias * 2 ** (exponent(max z of primitive) - number of bits in mantissa) +
// slopeScale * maxSlope
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdSetDepthBias.html
// https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516269-setdepthbias
//
// To get a final bias of 0.25 for primitives with z = 0.25, we can use
// depthBias = 0.25 / (2 ** (-2 - 23)) = 8388608.
const kPointTwoFiveBiasForPointTwoFiveZOnFloat = 8388608;
class DepthBiasTest extends AllFeaturesMaxLimitsGPUTest {
runDepthBiasTestInternal(
depthFormat: DepthStencilFormat,
{
quadAngle,
bias,
biasSlopeScale,
biasClamp,
initialDepth,
}: {
quadAngle: QuadAngle;
bias: number;
biasSlopeScale: number;
biasClamp: number;
initialDepth: number;
}
): { renderTarget: GPUTexture; depthTexture: GPUTexture } {
const renderTargetFormat = 'rgba8unorm';
let vertexShaderCode: string;
switch (quadAngle) {
case QuadAngle.Flat:
// Draw a square at z = 0.25.
vertexShaderCode = `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
var pos = 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.25, 1.0);
}
`;
break;
case QuadAngle.TiltedX:
// Draw a square ranging from 0 to 0.5, bottom to top.
vertexShaderCode = `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
var pos = array<vec3<f32>, 6>(
vec3<f32>(-1.0, -1.0, 0.0),
vec3<f32>( 1.0, -1.0, 0.0),
vec3<f32>(-1.0, 1.0, 0.5),
vec3<f32>(-1.0, 1.0, 0.5),
vec3<f32>( 1.0, -1.0, 0.0),
vec3<f32>( 1.0, 1.0, 0.5));
return vec4<f32>(pos[VertexIndex], 1.0);
}
`;
break;
default:
unreachable();
}
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: depthFormat,
sampleCount: 1,
mipLevelCount: 1,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: depthTexture.createView(),
depthLoadOp: isDepthTextureFormat(depthFormat) ? 'clear' : undefined,
depthStoreOp: isDepthTextureFormat(depthFormat) ? 'store' : undefined,
stencilLoadOp: isStencilTextureFormat(depthFormat) ? 'clear' : undefined,
stencilStoreOp: isStencilTextureFormat(depthFormat) ? 'store' : undefined,
depthClearValue: initialDepth,
};
const encoder = this.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
loadOp: 'load',
storeOp: 'store',
},
],
depthStencilAttachment,
});
let depthCompare: GPUCompareFunction = 'always';
if (depthFormat !== 'depth32float') {
depthCompare = 'greater';
}
const testState = {
format: depthFormat,
depthCompare,
depthWriteEnabled: true,
depthBias: bias,
depthBiasSlopeScale: biasSlopeScale,
depthBiasClamp: biasClamp,
} as const;
// Draw a square with the given depth state and bias values.
const testPipeline = this.createRenderPipelineForTest(vertexShaderCode, testState);
pass.setPipeline(testPipeline);
pass.draw(6);
pass.end();
this.device.queue.submit([encoder.finish()]);
return { renderTarget, depthTexture };
}
runDepthBiasTest(
depthFormat: EncodableTextureFormat & DepthStencilFormat,
{
quadAngle,
bias,
biasSlopeScale,
biasClamp,
_expectedDepth,
}: {
quadAngle: QuadAngle;
bias: number;
biasSlopeScale: number;
biasClamp: number;
_expectedDepth: number;
}
) {
const { depthTexture } = this.runDepthBiasTestInternal(depthFormat, {
quadAngle,
bias,
biasSlopeScale,
biasClamp,
initialDepth: 0,
});
const expColor = { Depth: _expectedDepth };
const expTexelView = TexelView.fromTexelsAsColors(depthFormat, _coords => expColor);
ttu.expectTexelViewComparisonIsOkInTexture(
this,
{ texture: depthTexture },
expTexelView,
[1, 1]
);
}
runDepthBiasTestFor24BitFormat(
depthFormat: DepthStencilFormat,
{
quadAngle,
bias,
biasSlopeScale,
biasClamp,
_expectedColor,
}: {
quadAngle: QuadAngle;
bias: number;
biasSlopeScale: number;
biasClamp: number;
_expectedColor: Float32Array;
}
) {
const { renderTarget } = this.runDepthBiasTestInternal(depthFormat, {
quadAngle,
bias,
biasSlopeScale,
biasClamp,
initialDepth: 0.4,
});
const renderTargetFormat = 'rgba8unorm';
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(
vertex: string,
depthStencil: GPUDepthStencilState
): GPURenderPipeline {
return this.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: this.device.createShaderModule({
code: vertex,
}),
entryPoint: 'main',
},
fragment: {
targets: [{ format: 'rgba8unorm' }],
module: this.device.createShaderModule({
code: `
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
},
depthStencil,
});
}
}
export const g = makeTestGroup(DepthBiasTest);
g.test('depth_bias')
.desc(
`
Tests that a square with different depth bias values like 'positive', 'negative',
'slope', 'clamp', etc. is drawn as expected.
`
)
.params(u =>
u //
.combineWithParams([
{
quadAngle: QuadAngle.Flat,
bias: kPointTwoFiveBiasForPointTwoFiveZOnFloat,
biasSlopeScale: 0,
biasClamp: 0,
_expectedDepth: 0.5,
},
{
quadAngle: QuadAngle.Flat,
bias: kPointTwoFiveBiasForPointTwoFiveZOnFloat,
biasSlopeScale: 0,
biasClamp: 0.125,
_expectedDepth: 0.375,
},
{
quadAngle: QuadAngle.Flat,
bias: -kPointTwoFiveBiasForPointTwoFiveZOnFloat,
biasSlopeScale: 0,
biasClamp: 0.125,
_expectedDepth: 0,
},
{
quadAngle: QuadAngle.Flat,
bias: -kPointTwoFiveBiasForPointTwoFiveZOnFloat,
biasSlopeScale: 0,
biasClamp: -0.125,
_expectedDepth: 0.125,
},
{
quadAngle: QuadAngle.TiltedX,
bias: 0,
biasSlopeScale: 0,
biasClamp: 0,
_expectedDepth: 0.25,
},
{
quadAngle: QuadAngle.TiltedX,
bias: 0,
biasSlopeScale: 1,
biasClamp: 0,
_expectedDepth: 0.75,
},
{
quadAngle: QuadAngle.TiltedX,
bias: 0,
biasSlopeScale: -0.5,
biasClamp: 0,
_expectedDepth: 0,
},
] as const)
)
.beforeAllSubcases(t => {
t.skipIf(
t.isCompatibility && t.params.biasClamp !== 0,
'non zero depthBiasClamp is not supported in compatibility mode'
);
})
.fn(t => {
t.runDepthBiasTest('depth32float', t.params);
});
g.test('depth_bias_24bit_format')
.desc(
`
Tests that a square with different depth bias values like 'positive', 'negative',
'slope', 'clamp', etc. is drawn as expected with 24 bit depth format.
TODO: Enhance these tests by reading back the depth (emulating the copy using texture sampling)
and checking the result directly, like the non-24-bit depth tests, instead of just relying on
whether the depth test passes or fails.
`
)
.params(u =>
u //
.combine('format', ['depth24plus', 'depth24plus-stencil8'] as const)
.combineWithParams([
{
quadAngle: QuadAngle.Flat,
bias: 0.25 * (1 << 25),
biasSlopeScale: 0,
biasClamp: 0,
_expectedColor: new Float32Array([1.0, 0.0, 0.0, 1.0]),
},
{
quadAngle: QuadAngle.TiltedX,
bias: 0.25 * (1 << 25),
biasSlopeScale: 1,
biasClamp: 0,
_expectedColor: new Float32Array([1.0, 0.0, 0.0, 1.0]),
},
{
quadAngle: QuadAngle.Flat,
bias: 0.25 * (1 << 25),
biasSlopeScale: 0,
biasClamp: 0.1,
_expectedColor: new Float32Array([0.0, 0.0, 0.0, 0.0]),
},
] as const)
)
.beforeAllSubcases(t => {
t.skipIf(
t.isCompatibility && t.params.biasClamp !== 0,
'non zero depthBiasClamp is not supported in compatibility mode'
);
})
.fn(t => {
const { format } = t.params;
t.runDepthBiasTestFor24BitFormat(format, t.params);
});