Add support for depthBias, depthBiasSlope, and depthBiasClamp

Bug: dawn:524
Change-Id: I2586aadbc326f58889314a2d10794bcc0572aaba
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/28300
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index b7d5187..f4a2209 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -540,6 +540,26 @@
         return mRasterizationState.frontFace;
     }
 
+    bool RenderPipelineBase::IsDepthBiasEnabled() const {
+        ASSERT(!IsError());
+        return mRasterizationState.depthBias != 0 || mRasterizationState.depthBiasSlopeScale != 0;
+    }
+
+    int32_t RenderPipelineBase::GetDepthBias() const {
+        ASSERT(!IsError());
+        return mRasterizationState.depthBias;
+    }
+
+    float RenderPipelineBase::GetDepthBiasSlopeScale() const {
+        ASSERT(!IsError());
+        return mRasterizationState.depthBiasSlopeScale;
+    }
+
+    float RenderPipelineBase::GetDepthBiasClamp() const {
+        ASSERT(!IsError());
+        return mRasterizationState.depthBiasClamp;
+    }
+
     ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments>
     RenderPipelineBase::GetColorAttachmentsMask() const {
         ASSERT(!IsError());
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index 817a3e5..db913a2 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -76,6 +76,10 @@
         wgpu::PrimitiveTopology GetPrimitiveTopology() const;
         wgpu::CullMode GetCullMode() const;
         wgpu::FrontFace GetFrontFace() const;
+        bool IsDepthBiasEnabled() const;
+        int32_t GetDepthBias() const;
+        float GetDepthBiasSlopeScale() const;
+        float GetDepthBiasClamp() const;
 
         ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> GetColorAttachmentsMask() const;
         bool HasDepthStencilAttachment() const;
diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
index 5cdfc24..4459345 100644
--- a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
+++ b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
@@ -346,10 +346,9 @@
         descriptorD3D12.RasterizerState.CullMode = D3D12CullMode(GetCullMode());
         descriptorD3D12.RasterizerState.FrontCounterClockwise =
             (GetFrontFace() == wgpu::FrontFace::CCW) ? TRUE : FALSE;
-        descriptorD3D12.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
-        descriptorD3D12.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
-        descriptorD3D12.RasterizerState.SlopeScaledDepthBias =
-            D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
+        descriptorD3D12.RasterizerState.DepthBias = GetDepthBias();
+        descriptorD3D12.RasterizerState.DepthBiasClamp = GetDepthBiasClamp();
+        descriptorD3D12.RasterizerState.SlopeScaledDepthBias = GetDepthBiasSlopeScale();
         descriptorD3D12.RasterizerState.DepthClipEnable = TRUE;
         descriptorD3D12.RasterizerState.MultisampleEnable = (GetSampleCount() > 1) ? TRUE : FALSE;
         descriptorD3D12.RasterizerState.AntialiasedLineEnable = FALSE;
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 2a9d4aa..dbd9943 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -1143,6 +1143,9 @@
                     [encoder setDepthStencilState:newPipeline->GetMTLDepthStencilState()];
                     [encoder setFrontFacingWinding:newPipeline->GetMTLFrontFace()];
                     [encoder setCullMode:newPipeline->GetMTLCullMode()];
+                    [encoder setDepthBias:newPipeline->GetDepthBias()
+                               slopeScale:newPipeline->GetDepthBiasSlopeScale()
+                                    clamp:newPipeline->GetDepthBiasClamp()];
                     newPipeline->Encode(encoder);
 
                     lastPipeline = newPipeline;
diff --git a/src/dawn_native/opengl/RenderPipelineGL.cpp b/src/dawn_native/opengl/RenderPipelineGL.cpp
index bc5882b..828f4cc 100644
--- a/src/dawn_native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn_native/opengl/RenderPipelineGL.cpp
@@ -264,6 +264,19 @@
             gl.Disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
         }
 
+        if (IsDepthBiasEnabled()) {
+            gl.Enable(GL_POLYGON_OFFSET_FILL);
+            float depthBias = GetDepthBias();
+            float slopeScale = GetDepthBiasSlopeScale();
+            if (gl.PolygonOffsetClamp != nullptr) {
+                gl.PolygonOffsetClamp(slopeScale, depthBias, GetDepthBiasClamp());
+            } else {
+                gl.PolygonOffset(slopeScale, depthBias);
+            }
+        } else {
+            gl.Disable(GL_POLYGON_OFFSET_FILL);
+        }
+
         for (ColorAttachmentIndex attachmentSlot : IterateBitSet(GetColorAttachmentsMask())) {
             ApplyColorState(gl, attachmentSlot, GetColorStateDescriptor(attachmentSlot));
         }
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index b3977b1..981a940 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -298,6 +298,8 @@
         usedKnobs.features.imageCubeArray = VK_TRUE;
         // Always require fragmentStoresAndAtomics because it is required by end2end tests.
         usedKnobs.features.fragmentStoresAndAtomics = VK_TRUE;
+        // Always require depthBiasClamp because it is a core Dawn feature
+        usedKnobs.features.depthBiasClamp = VK_TRUE;
 
         if (IsRobustnessEnabled()) {
             usedKnobs.features.robustBufferAccess = VK_TRUE;
diff --git a/src/dawn_native/vulkan/RenderPipelineVk.cpp b/src/dawn_native/vulkan/RenderPipelineVk.cpp
index 41536a2..0389d6d 100644
--- a/src/dawn_native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn_native/vulkan/RenderPipelineVk.cpp
@@ -379,10 +379,10 @@
         rasterization.polygonMode = VK_POLYGON_MODE_FILL;
         rasterization.cullMode = VulkanCullMode(GetCullMode());
         rasterization.frontFace = VulkanFrontFace(GetFrontFace());
-        rasterization.depthBiasEnable = VK_FALSE;
-        rasterization.depthBiasConstantFactor = 0.0f;
-        rasterization.depthBiasClamp = 0.0f;
-        rasterization.depthBiasSlopeFactor = 0.0f;
+        rasterization.depthBiasEnable = IsDepthBiasEnabled();
+        rasterization.depthBiasConstantFactor = GetDepthBias();
+        rasterization.depthBiasClamp = GetDepthBiasClamp();
+        rasterization.depthBiasSlopeFactor = GetDepthBiasSlopeScale();
         rasterization.lineWidth = 1.0f;
 
         VkPipelineMultisampleStateCreateInfo multisample;
@@ -432,12 +432,11 @@
         colorBlend.blendConstants[2] = 0.0f;
         colorBlend.blendConstants[3] = 0.0f;
 
-        // Tag all state as dynamic but stencil masks.
+        // Tag all state as dynamic but stencil masks and depth bias.
         VkDynamicState dynamicStates[] = {
-            VK_DYNAMIC_STATE_VIEWPORT,          VK_DYNAMIC_STATE_SCISSOR,
-            VK_DYNAMIC_STATE_LINE_WIDTH,        VK_DYNAMIC_STATE_DEPTH_BIAS,
-            VK_DYNAMIC_STATE_BLEND_CONSTANTS,   VK_DYNAMIC_STATE_DEPTH_BOUNDS,
-            VK_DYNAMIC_STATE_STENCIL_REFERENCE,
+            VK_DYNAMIC_STATE_VIEWPORT,     VK_DYNAMIC_STATE_SCISSOR,
+            VK_DYNAMIC_STATE_LINE_WIDTH,   VK_DYNAMIC_STATE_BLEND_CONSTANTS,
+            VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
         };
         VkPipelineDynamicStateCreateInfo dynamic;
         dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 0077695..4115a48 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -275,6 +275,7 @@
     "end2end/CullingTests.cpp",
     "end2end/DebugMarkerTests.cpp",
     "end2end/DeprecatedAPITests.cpp",
+    "end2end/DepthBiasTests.cpp",
     "end2end/DepthSamplingTests.cpp",
     "end2end/DepthStencilCopyTests.cpp",
     "end2end/DepthStencilStateTests.cpp",
diff --git a/src/tests/end2end/DepthBiasTests.cpp b/src/tests/end2end/DepthBiasTests.cpp
new file mode 100644
index 0000000..505e89d
--- /dev/null
+++ b/src/tests/end2end/DepthBiasTests.cpp
@@ -0,0 +1,381 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/Constants.h"
+#include "common/Math.h"
+#include "tests/DawnTest.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/TextureFormatUtils.h"
+#include "utils/WGPUHelpers.h"
+
+constexpr static unsigned int kRTSize = 2;
+
+enum class QuadAngle { Flat, TiltedX };
+
+class DepthBiasTests : public DawnTest {
+  protected:
+    void RunDepthBiasTest(wgpu::TextureFormat depthFormat,
+                          float depthClear,
+                          QuadAngle quadAngle,
+                          int32_t bias,
+                          float biasSlopeScale,
+                          float biasClamp) {
+        const char* vertexSource = nullptr;
+        switch (quadAngle) {
+            case QuadAngle::Flat:
+                // Draw a square at z = 0.25
+                vertexSource = R"(
+    #version 450
+    void main() {
+        const vec2 pos[6] = vec2[6](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f,  1.f),
+                                    vec2(-1.f,  1.f), vec2(1.f, -1.f), vec2( 1.f,  1.f));
+        gl_Position = vec4(pos[gl_VertexIndex], 0.25f, 1.f);
+    })";
+                break;
+
+            case QuadAngle::TiltedX:
+                // Draw a square ranging from 0 to 0.5, bottom to top
+                vertexSource = R"(
+    #version 450
+    void main() {
+        const vec3 pos[6] = vec3[6](vec3(-1.f, -1.f, 0.f ), vec3(1.f, -1.f, 0.f), vec3(-1.f,  1.f, 0.5f),
+                                    vec3(-1.f,  1.f, 0.5f), vec3(1.f, -1.f, 0.f), vec3( 1.f,  1.f, 0.5f));
+        gl_Position = vec4(pos[gl_VertexIndex], 1.f);
+    })";
+                break;
+        }
+
+        wgpu::ShaderModule vertexModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexSource);
+
+        wgpu::ShaderModule fragmentModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+    #version 450
+    layout(location = 0) out vec4 fragColor;
+    void main() {
+        fragColor = vec4(1.f, 0.f, 0.f, 1.f);
+    })");
+
+        {
+            wgpu::TextureDescriptor descriptor;
+            descriptor.size = {kRTSize, kRTSize, 1};
+            descriptor.format = depthFormat;
+            descriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc;
+            mDepthTexture = device.CreateTexture(&descriptor);
+        }
+
+        {
+            wgpu::TextureDescriptor descriptor;
+            descriptor.size = {kRTSize, kRTSize, 1};
+            descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+            descriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc;
+            mRenderTarget = device.CreateTexture(&descriptor);
+        }
+
+        // Create a render pass which clears depth to depthClear
+        utils::ComboRenderPassDescriptor renderPassDesc({mRenderTarget.CreateView()},
+                                                        mDepthTexture.CreateView());
+        renderPassDesc.cDepthStencilAttachmentInfo.clearDepth = depthClear;
+
+        // Create a render pipeline to render the quad
+        utils::ComboRenderPipelineDescriptor renderPipelineDesc(device);
+
+        renderPipelineDesc.cRasterizationState.depthBias = bias;
+        renderPipelineDesc.cRasterizationState.depthBiasSlopeScale = biasSlopeScale;
+        renderPipelineDesc.cRasterizationState.depthBiasClamp = biasClamp;
+
+        renderPipelineDesc.vertexStage.module = vertexModule;
+        renderPipelineDesc.cFragmentStage.module = fragmentModule;
+        renderPipelineDesc.cDepthStencilState.format = depthFormat;
+        renderPipelineDesc.cDepthStencilState.depthWriteEnabled = true;
+
+        if (depthFormat != wgpu::TextureFormat::Depth32Float) {
+            renderPipelineDesc.cDepthStencilState.depthCompare = wgpu::CompareFunction::Greater;
+        }
+
+        renderPipelineDesc.depthStencilState = &renderPipelineDesc.cDepthStencilState;
+
+        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc);
+
+        // Draw the quad (two triangles)
+        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPassDesc);
+        pass.SetPipeline(pipeline);
+        pass.Draw(6);
+        pass.EndPass();
+
+        wgpu::CommandBuffer commands = commandEncoder.Finish();
+        queue.Submit(1, &commands);
+    }
+
+    // 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
+    static constexpr int32_t kPointTwoFiveBiasForPointTwoFiveZOnFloat = 8388608;
+
+    wgpu::Texture mDepthTexture;
+    wgpu::Texture mRenderTarget;
+};
+
+// Test adding positive bias to output
+TEST_P(DepthBiasTests, PositiveBiasOnFloat) {
+    // NVIDIA GPUs under Vulkan seem to be using a different scale than everyone else.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsNvidia());
+
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // OpenGL uses a different scale than the other APIs
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    // Draw quad flat on z = 0.25 with 0.25 bias
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::Flat,
+                     kPointTwoFiveBiasForPointTwoFiveZOnFloat, 0, 0);
+
+    // Quad at z = 0.25 + 0.25 bias = 0.5
+    std::vector<float> expected = {
+        0.5, 0.5,  //
+        0.5, 0.5,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding positive bias to output with a clamp
+TEST_P(DepthBiasTests, PositiveBiasOnFloatWithClamp) {
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // Clamping support in OpenGL is spotty
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    // Draw quad flat on z = 0.25 with 0.25 bias clamped at 0.125.
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::Flat,
+                     kPointTwoFiveBiasForPointTwoFiveZOnFloat, 0, 0.125);
+
+    // Quad at z = 0.25 + min(0.25 bias, 0.125 clamp) = 0.375
+    std::vector<float> expected = {
+        0.375, 0.375,  //
+        0.375, 0.375,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding negative bias to output
+TEST_P(DepthBiasTests, NegativeBiasOnFloat) {
+    // NVIDIA GPUs seems to be using a different scale than everyone else
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsNvidia());
+
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // OpenGL uses a different scale than the other APIs
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    // Draw quad flat on z = 0.25 with -0.25 bias, depth clear of 0.125
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0.125, QuadAngle::Flat,
+                     -kPointTwoFiveBiasForPointTwoFiveZOnFloat, 0, 0);
+
+    // Quad at z = 0.25 - 0.25 bias = 0
+    std::vector<float> expected = {
+        0.0, 0.0,  //
+        0.0, 0.0,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding negative bias to output with a clamp
+TEST_P(DepthBiasTests, NegativeBiasOnFloatWithClamp) {
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // Clamping support in OpenGL is spotty
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    // Draw quad flat on z = 0.25 with -0.25 bias clamped at -0.125.
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::Flat,
+                     -kPointTwoFiveBiasForPointTwoFiveZOnFloat, 0, -0.125);
+
+    // Quad at z = 0.25 + max(-0.25 bias, -0.125 clamp) = 0.125
+    std::vector<float> expected = {
+        0.125, 0.125,  //
+        0.125, 0.125,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding positive infinite slope bias to output
+TEST_P(DepthBiasTests, PositiveInfinitySlopeBiasOnFloat) {
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // NVIDIA GPUs do not clamp values to 1 when using Inf slope bias.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsNvidia());
+
+    // Draw quad with z from 0 to 0.5 with inf slope bias
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0.125, QuadAngle::TiltedX, 0,
+                     std::numeric_limits<float>::infinity(), 0);
+
+    // Value at the center of the pixel + (0.25 slope * Inf slope bias) = 1 (clamped)
+    std::vector<float> expected = {
+        1.0, 1.0,  //
+        1.0, 1.0,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding positive infinite slope bias to output
+TEST_P(DepthBiasTests, NegativeInfinityBiasOnFloat) {
+    // SwiftShader incorrectly uses depthBias directly when calculating bias.
+    // TODO(enrico.galli@intel.com): Remove once it has been fixed upstream.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
+
+    // NVIDIA GPUs do not clamp values to 0 when using -Inf slope bias.
+    DAWN_SKIP_TEST_IF(IsVulkan() && IsNvidia());
+
+    // Draw quad with z from 0 to 0.5 with -inf slope bias
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0.125, QuadAngle::TiltedX, 0,
+                     -std::numeric_limits<float>::infinity(), 0);
+
+    // Value at the center of the pixel + (0.25 slope * -Inf slope bias) = 0 (clamped)
+    std::vector<float> expected = {
+        0.0, 0.0,  //
+        0.0, 0.0,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test tiledX quad with no bias
+TEST_P(DepthBiasTests, NoBiasTiltedXOnFloat) {
+    // Draw quad with z from 0 to 0.5 with no bias
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::TiltedX, 0, 0, 0);
+
+    // Depth values of TiltedX quad. Values at the center of the pixels.
+    std::vector<float> expected = {
+        0.375, 0.375,  //
+        0.125, 0.125,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding positive slope bias to output
+TEST_P(DepthBiasTests, PositiveSlopeBiasOnFloat) {
+    // Draw quad with z from 0 to 0.5 with a slope bias of 1
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::TiltedX, 0, 1, 0);
+
+    // Value at the center of the pixel + (0.25 slope * 1.0 slope bias)
+    std::vector<float> expected = {
+        0.625, 0.625,  //
+        0.375, 0.375,  //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding negative half slope bias to output
+TEST_P(DepthBiasTests, NegativeHalfSlopeBiasOnFloat) {
+    // Draw quad with z from 0 to 0.5 with a slope bias of -0.5
+    RunDepthBiasTest(wgpu::TextureFormat::Depth32Float, 0, QuadAngle::TiltedX, 0, -0.5, 0);
+
+    // Value at the center of the pixel + (0.25 slope * -0.5 slope bias)
+    std::vector<float> expected = {
+        0.25, 0.25,  //
+        0.0, 0.0,    //
+    };
+
+    EXPECT_TEXTURE_EQ(expected.data(), mDepthTexture, 0, 0, kRTSize, kRTSize, 0, 0,
+                      wgpu::TextureAspect::DepthOnly);
+}
+
+// Test adding positive bias to output
+TEST_P(DepthBiasTests, PositiveBiasOn24bit) {
+    // Draw quad flat on z = 0.25 with 0.25 bias
+    RunDepthBiasTest(wgpu::TextureFormat::Depth24PlusStencil8, 0.4f, QuadAngle::Flat,
+                     0.25f * (1 << 25), 0, 0);
+
+    // Only the bottom left quad has colors. 0.5 quad > 0.4 clear.
+    // TODO(enrico.galli@intel.com): Switch to depth sampling once feature has been enabled.
+    std::vector<RGBA8> expected = {
+        RGBA8::kRed, RGBA8::kRed,  //
+        RGBA8::kRed, RGBA8::kRed,  //
+    };
+
+    EXPECT_TEXTURE_RGBA8_EQ(expected.data(), mRenderTarget, 0, 0, kRTSize, kRTSize, 0, 0);
+}
+
+// Test adding positive bias to output with a clamp
+TEST_P(DepthBiasTests, PositiveBiasOn24bitWithClamp) {
+    // Clamping support in OpenGL is spotty
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    // Draw quad flat on z = 0.25 with 0.25 bias clamped at 0.125.
+    RunDepthBiasTest(wgpu::TextureFormat::Depth24PlusStencil8, 0.4f, QuadAngle::Flat,
+                     0.25f * (1 << 25), 0, 0.1f);
+
+    // Since we cleared with a depth of 0.4 and clamped bias at 0.4, the depth test will fail. 0.25
+    // + 0.125 < 0.4 clear.
+    // TODO(enrico.galli@intel.com): Switch to depth sampling once feature has been enabled.
+    std::vector<RGBA8> zero = {
+        RGBA8::kZero, RGBA8::kZero,  //
+        RGBA8::kZero, RGBA8::kZero,  //
+    };
+
+    EXPECT_TEXTURE_RGBA8_EQ(zero.data(), mRenderTarget, 0, 0, kRTSize, kRTSize, 0, 0);
+}
+
+// Test adding positive bias to output
+TEST_P(DepthBiasTests, PositiveSlopeBiasOn24bit) {
+    // Draw quad with z from 0 to 0.5 with a slope bias of 1
+    RunDepthBiasTest(wgpu::TextureFormat::Depth24PlusStencil8, 0.4f, QuadAngle::TiltedX, 0, 1, 0);
+
+    // Only the top half of the quad has a depth > 0.4 clear
+    // TODO(enrico.galli@intel.com): Switch to depth sampling once feature has been enabled.
+    std::vector<RGBA8> expected = {
+        RGBA8::kRed, RGBA8::kRed,    //
+        RGBA8::kZero, RGBA8::kZero,  //
+    };
+
+    EXPECT_TEXTURE_RGBA8_EQ(expected.data(), mRenderTarget, 0, 0, kRTSize, kRTSize, 0, 0);
+}
+
+DAWN_INSTANTIATE_TEST(DepthBiasTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      VulkanBackend());