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 { var pos = array, 6>( vec2(-1.0, -1.0), vec2( 1.0, -1.0), vec2(-1.0, 1.0), vec2(-1.0, 1.0), vec2( 1.0, -1.0), vec2( 1.0, 1.0)); return vec4(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 { var pos = array, 6>( vec3(-1.0, -1.0, 0.0), vec3( 1.0, -1.0, 0.0), vec3(-1.0, 1.0, 0.5), vec3(-1.0, 1.0, 0.5), vec3( 1.0, -1.0, 0.0), vec3( 1.0, 1.0, 0.5)); return vec4(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 { return vec4(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); });