| 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); |
| }); |