Vulkan: Clamp @builtin(frag_depth) with push constant values

Start using Tint's ClampFragDepth transform in the Vulkan backend when
needed in order to correctly clamp @builtin(frag_depth) on Vulkan. Do
this by always reserving 8 bytes of push constant space to contain the
f32 min and max values from the last viewport command.

Reenables relevant CTS tests that were suppressed on Vulkan.

Bug: dawn:1125, dawn:1576, dawn:1616
Change-Id: I38f4f6c3c51c99b5e591a780fea9859537529534
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105642
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/include/tint/tint.h b/include/tint/tint.h
index c71682f..02ac423 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -26,6 +26,7 @@
 #include "src/tint/inspector/inspector.h"
 #include "src/tint/reader/reader.h"
 #include "src/tint/transform/binding_remapper.h"
+#include "src/tint/transform/clamp_frag_depth.h"
 #include "src/tint/transform/first_index_offset.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/multiplanar_external_texture.h"
diff --git a/src/dawn/native/Pipeline.cpp b/src/dawn/native/Pipeline.cpp
index 581472d..85a985b 100644
--- a/src/dawn/native/Pipeline.cpp
+++ b/src/dawn/native/Pipeline.cpp
@@ -216,6 +216,10 @@
     return mStages;
 }
 
+bool PipelineBase::HasStage(SingleShaderStage stage) const {
+    return mStageMask & StageBit(stage);
+}
+
 wgpu::ShaderStage PipelineBase::GetStageMask() const {
     return mStageMask;
 }
diff --git a/src/dawn/native/Pipeline.h b/src/dawn/native/Pipeline.h
index fa0b65b..0a88c81 100644
--- a/src/dawn/native/Pipeline.h
+++ b/src/dawn/native/Pipeline.h
@@ -59,6 +59,7 @@
     const RequiredBufferSizes& GetMinBufferSizes() const;
     const ProgrammableStage& GetStage(SingleShaderStage stage) const;
     const PerStage<ProgrammableStage>& GetAllStages() const;
+    bool HasStage(SingleShaderStage stage) const;
     wgpu::ShaderStage GetStageMask() const;
 
     ResultOrError<Ref<BindGroupLayoutBase>> GetBindGroupLayout(uint32_t groupIndex);
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 60e8125..1f0b263 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -643,6 +643,10 @@
         }
     }
 
+    if (HasStage(SingleShaderStage::Fragment)) {
+        mUsesFragDepth = GetStage(SingleShaderStage::Fragment).metadata->usesFragDepth;
+    }
+
     SetContentHash(ComputeContentHash());
     GetObjectTrackingList()->Track(this);
 
@@ -829,22 +833,24 @@
 
 const AttachmentState* RenderPipelineBase::GetAttachmentState() const {
     ASSERT(!IsError());
-
     return mAttachmentState.Get();
 }
 
 bool RenderPipelineBase::WritesDepth() const {
     ASSERT(!IsError());
-
     return mWritesDepth;
 }
 
 bool RenderPipelineBase::WritesStencil() const {
     ASSERT(!IsError());
-
     return mWritesStencil;
 }
 
+bool RenderPipelineBase::UsesFragDepth() const {
+    ASSERT(!IsError());
+    return mUsesFragDepth;
+}
+
 size_t RenderPipelineBase::ComputeContentHash() {
     ObjectContentHasher recorder;
 
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index afba4a7..a1eaee6 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -101,6 +101,7 @@
     bool IsAlphaToCoverageEnabled() const;
     bool WritesDepth() const;
     bool WritesStencil() const;
+    bool UsesFragDepth() const;
 
     const AttachmentState* GetAttachmentState() const;
 
@@ -140,6 +141,7 @@
     bool mUnclippedDepth = false;
     bool mWritesDepth = false;
     bool mWritesStencil = false;
+    bool mUsesFragDepth = false;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 812980d..8a6c4e1 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -646,6 +646,7 @@
         if (entryPoint.sample_index_used) {
             totalInterStageShaderComponents += 1;
         }
+        metadata->usesFragDepth = entryPoint.frag_depth_used;
 
         metadata->totalInterStageShaderComponents = totalInterStageShaderComponents;
         DelayedInvalidIf(totalInterStageShaderComponents > maxInterStageShaderComponents,
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index af63e85..5612d15 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -245,6 +245,7 @@
     std::unordered_set<std::string> initializedOverrides;
 
     bool usesNumWorkgroups = false;
+    bool usesFragDepth = false;
     // Used at render pipeline validation.
     bool usesSampleMaskOutput = false;
 };
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index f1f5131..397591a 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -1490,9 +1490,13 @@
                            slopeScale:newPipeline->GetDepthBiasSlopeScale()
                                 clamp:newPipeline->GetDepthBiasClamp()];
                 if (@available(macOS 10.11, iOS 11.0, *)) {
-                    MTLDepthClipMode clipMode = newPipeline->HasUnclippedDepth()
-                                                    ? MTLDepthClipModeClamp
-                                                    : MTLDepthClipModeClip;
+                    // When using @builtin(frag_depth) we need to clamp to the viewport, otherwise
+                    // Metal writes the raw value to the depth buffer, which doesn't match other
+                    // APIs.
+                    MTLDepthClipMode clipMode =
+                        (newPipeline->UsesFragDepth() || newPipeline->HasUnclippedDepth())
+                            ? MTLDepthClipModeClamp
+                            : MTLDepthClipModeClip;
                     [encoder setDepthClipMode:clipMode];
                 }
                 newPipeline->Encode(encoder);
diff --git a/src/dawn/native/vulkan/AdapterVk.cpp b/src/dawn/native/vulkan/AdapterVk.cpp
index be468e8..7bcce2a 100644
--- a/src/dawn/native/vulkan/AdapterVk.cpp
+++ b/src/dawn/native/vulkan/AdapterVk.cpp
@@ -232,7 +232,9 @@
         mSupportedFeatures.EnableFeature(Feature::ChromiumExperimentalDp4a);
     }
 
-    if (mDeviceInfo.HasExt(DeviceExt::DepthClipEnable) &&
+    // unclippedDepth=true translates to depthClipEnable=false, depthClamp=true
+    if (mDeviceInfo.features.depthClamp == VK_TRUE &&
+        mDeviceInfo.HasExt(DeviceExt::DepthClipEnable) &&
         mDeviceInfo.depthClipEnableFeatures.depthClipEnable == VK_TRUE) {
         mSupportedFeatures.EnableFeature(Feature::DepthClipControl);
     }
diff --git a/src/dawn/native/vulkan/CommandBufferVk.cpp b/src/dawn/native/vulkan/CommandBufferVk.cpp
index ef88a92..b67b0df 100644
--- a/src/dawn/native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn/native/vulkan/CommandBufferVk.cpp
@@ -1108,6 +1108,23 @@
     DescriptorSetTracker descriptorSets = {};
     RenderPipeline* lastPipeline = nullptr;
 
+    // Tracking for the push constants needed by the ClampFragDepth transform.
+    // TODO(dawn:1125): Avoid the need for this when the depthClamp feature is available, but doing
+    // so would require fixing issue dawn:1576 first to have more dynamic push constant usage. (and
+    // also additional tests that the dirtying logic here is correct so with a Toggle we can test it
+    // on our infra).
+    ClampFragDepthArgs clampFragDepthArgs = {0.0f, 1.0f};
+    bool clampFragDepthArgsDirty = true;
+    auto ApplyClampFragDepthArgs = [&]() {
+        if (!clampFragDepthArgsDirty || lastPipeline == nullptr) {
+            return;
+        }
+        device->fn.CmdPushConstants(commands, ToBackend(lastPipeline->GetLayout())->GetHandle(),
+                                    VK_SHADER_STAGE_FRAGMENT_BIT, kClampFragDepthArgsOffset,
+                                    kClampFragDepthArgsSize, &clampFragDepthArgs);
+        clampFragDepthArgsDirty = false;
+    };
+
     auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) {
         switch (type) {
             case Command::Draw: {
@@ -1231,6 +1248,9 @@
                 lastPipeline = pipeline;
 
                 descriptorSets.OnSetPipeline(pipeline);
+
+                // Apply the deferred min/maxDepth push constants update if needed.
+                ApplyClampFragDepthArgs();
                 break;
             }
 
@@ -1302,6 +1322,12 @@
                 }
 
                 device->fn.CmdSetViewport(commands, 0, 1, &viewport);
+
+                // Try applying the push constants that contain min/maxDepth immediately. This can
+                // be deferred if no pipeline is currently bound.
+                clampFragDepthArgs = {viewport.minDepth, viewport.maxDepth};
+                clampFragDepthArgsDirty = true;
+                ApplyClampFragDepthArgs();
                 break;
             }
 
diff --git a/src/dawn/native/vulkan/ComputePipelineVk.cpp b/src/dawn/native/vulkan/ComputePipelineVk.cpp
index 49522e2..f607f7b 100644
--- a/src/dawn/native/vulkan/ComputePipelineVk.cpp
+++ b/src/dawn/native/vulkan/ComputePipelineVk.cpp
@@ -61,7 +61,8 @@
 
     ShaderModule::ModuleAndSpirv moduleAndSpirv;
     DAWN_TRY_ASSIGN(moduleAndSpirv,
-                    module->GetHandleAndSpirv(SingleShaderStage::Compute, computeStage, layout));
+                    module->GetHandleAndSpirv(SingleShaderStage::Compute, computeStage, layout,
+                                              /*clampFragDepth*/ false));
 
     createInfo.stage.module = moduleAndSpirv.module;
     createInfo.stage.pName = moduleAndSpirv.remappedEntryPoint;
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 7a7b61e..c71ce86 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -505,6 +505,7 @@
         ASSERT(deviceInfo.HasExt(DeviceExt::DepthClipEnable) &&
                deviceInfo.depthClipEnableFeatures.depthClipEnable == VK_TRUE);
 
+        usedKnobs.features.depthClamp = VK_TRUE;
         usedKnobs.depthClipEnableFeatures.depthClipEnable = VK_TRUE;
         featuresChain.Add(&usedKnobs.depthClipEnableFeatures,
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT);
diff --git a/src/dawn/native/vulkan/PipelineLayoutVk.cpp b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
index a47c4ed..69b0d23 100644
--- a/src/dawn/native/vulkan/PipelineLayoutVk.cpp
+++ b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
@@ -46,14 +46,20 @@
         numSetLayouts++;
     }
 
+    // Always reserve push constant space for the ClampFragDepthArgs.
+    VkPushConstantRange depthClampArgsRange;
+    depthClampArgsRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+    depthClampArgsRange.offset = kClampFragDepthArgsOffset;
+    depthClampArgsRange.size = kClampFragDepthArgsSize;
+
     VkPipelineLayoutCreateInfo createInfo;
     createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
     createInfo.pNext = nullptr;
     createInfo.flags = 0;
     createInfo.setLayoutCount = numSetLayouts;
     createInfo.pSetLayouts = AsVkArray(setLayouts.data());
-    createInfo.pushConstantRangeCount = 0;
-    createInfo.pPushConstantRanges = nullptr;
+    createInfo.pushConstantRangeCount = 1;
+    createInfo.pPushConstantRanges = &depthClampArgsRange;
 
     // Record cache key information now since the createInfo is not stored.
     StreamIn(&mCacheKey, stream::Iterable(cachedObjects.data(), numSetLayouts), createInfo);
diff --git a/src/dawn/native/vulkan/PipelineLayoutVk.h b/src/dawn/native/vulkan/PipelineLayoutVk.h
index 2b8f5cf..ede3aa3 100644
--- a/src/dawn/native/vulkan/PipelineLayoutVk.h
+++ b/src/dawn/native/vulkan/PipelineLayoutVk.h
@@ -24,6 +24,17 @@
 
 class Device;
 
+// 8 bytes of push constant data are always reserved in the Vulkan pipeline layouts to be used by
+// the code generated by the ClampFragDepth Tint transform. TODO(dawn:1576): Optimize usage of push
+// constants so that they are only added to a pipeline / pipeline layout if needed.
+struct ClampFragDepthArgs {
+    float min;
+    float max;
+};
+constexpr size_t kClampFragDepthArgsOffset = 0u;
+constexpr size_t kClampFragDepthArgsSize = sizeof(ClampFragDepthArgs);
+static_assert(kClampFragDepthArgsSize == 8u);
+
 class PipelineLayout final : public PipelineLayoutBase {
   public:
     static ResultOrError<Ref<PipelineLayout>> Create(Device* device,
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 0ca9421..5595bf0 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -343,45 +343,38 @@
     std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
     uint32_t stageCount = 0;
 
-    for (auto stage : IterateStages(this->GetStageMask())) {
-        VkPipelineShaderStageCreateInfo shaderStage;
-
+    auto AddShaderStage = [&](SingleShaderStage stage, VkShaderStageFlagBits vkStage,
+                              bool clampFragDepth) -> MaybeError {
         const ProgrammableStage& programmableStage = GetStage(stage);
-        ShaderModule* module = ToBackend(programmableStage.module.Get());
-
         ShaderModule::ModuleAndSpirv moduleAndSpirv;
         DAWN_TRY_ASSIGN(moduleAndSpirv,
-                        module->GetHandleAndSpirv(stage, programmableStage, layout));
-
-        shaderStage.module = moduleAndSpirv.module;
-        shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
-        shaderStage.pNext = nullptr;
-        shaderStage.flags = 0;
-        shaderStage.pSpecializationInfo = nullptr;
-        shaderStage.pName = moduleAndSpirv.remappedEntryPoint;
-
-        switch (stage) {
-            case dawn::native::SingleShaderStage::Vertex: {
-                shaderStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
-                break;
-            }
-            case dawn::native::SingleShaderStage::Fragment: {
-                shaderStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
-                break;
-            }
-            default: {
-                // For render pipeline only Vertex and Fragment stage is possible
-                DAWN_UNREACHABLE();
-                break;
-            }
-        }
-
-        DAWN_ASSERT(stageCount < 2);
-        shaderStages[stageCount] = shaderStage;
-        stageCount++;
-
+                        ToBackend(programmableStage.module)
+                            ->GetHandleAndSpirv(stage, programmableStage, layout, clampFragDepth));
         // Record cache key for each shader since it will become inaccessible later on.
         StreamIn(&mCacheKey, stream::Iterable(moduleAndSpirv.spirv, moduleAndSpirv.wordCount));
+
+        VkPipelineShaderStageCreateInfo* shaderStage = &shaderStages[stageCount];
+        shaderStage->module = moduleAndSpirv.module;
+        shaderStage->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+        shaderStage->pNext = nullptr;
+        shaderStage->flags = 0;
+        shaderStage->pSpecializationInfo = nullptr;
+        shaderStage->stage = vkStage;
+        shaderStage->pName = moduleAndSpirv.remappedEntryPoint;
+
+        stageCount++;
+        return {};
+    };
+
+    // Add the vertex stage that's always present.
+    DAWN_TRY(AddShaderStage(SingleShaderStage::Vertex, VK_SHADER_STAGE_VERTEX_BIT,
+                            /*clampFragDepth*/ false));
+
+    // Add the fragment stage if present.
+    if (GetStageMask() & wgpu::ShaderStage::Fragment) {
+        bool clampFragDepth = UsesFragDepth() && !HasUnclippedDepth();
+        DAWN_TRY(AddShaderStage(SingleShaderStage::Fragment, VK_SHADER_STAGE_FRAGMENT_BIT,
+                                clampFragDepth));
     }
 
     PipelineVertexInputStateCreateInfoTemporaryAllocations tempAllocations;
@@ -422,7 +415,7 @@
     rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
     rasterization.pNext = nullptr;
     rasterization.flags = 0;
-    rasterization.depthClampEnable = VK_FALSE;
+    rasterization.depthClampEnable = HasUnclippedDepth();
     rasterization.rasterizerDiscardEnable = VK_FALSE;
     rasterization.polygonMode = VK_POLYGON_MODE_FILL;
     rasterization.cullMode = VulkanCullMode(GetCullMode());
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index 4966e92..39fd0df 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -175,6 +175,7 @@
     X(bool, disableWorkgroupInit)                                                           \
     X(bool, disableSymbolRenaming)                                                          \
     X(bool, useZeroInitializeWorkgroupMemoryExtension)                                      \
+    X(bool, clampFragDepth)                                                                 \
     X(CacheKey::UnsafeUnkeyedValue<dawn::platform::Platform*>, tracePlatform)
 
 DAWN_MAKE_CACHE_REQUEST(SpirvCompilationRequest, SPIRV_COMPILATION_REQUEST_MEMBERS);
@@ -183,7 +184,8 @@
 ResultOrError<ShaderModule::ModuleAndSpirv> ShaderModule::GetHandleAndSpirv(
     SingleShaderStage stage,
     const ProgrammableStage& programmableStage,
-    const PipelineLayout* layout) {
+    const PipelineLayout* layout,
+    bool clampFragDepth) {
     TRACE_EVENT0(GetDevice()->GetPlatform(), General, "ShaderModuleVk::GetHandleAndSpirv");
 
     // If the shader was destroyed, we should never call this function.
@@ -258,6 +260,7 @@
     req.disableSymbolRenaming = GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming);
     req.useZeroInitializeWorkgroupMemoryExtension =
         GetDevice()->IsToggleEnabled(Toggle::VulkanUseZeroInitializeWorkgroupMemoryExtension);
+    req.clampFragDepth = clampFragDepth;
     req.tracePlatform = UnsafeUnkeyedValue(GetDevice()->GetPlatform());
     req.substituteOverrideConfig = std::move(substituteOverrideConfig);
 
@@ -305,6 +308,10 @@
                     std::move(r.substituteOverrideConfig).value());
             }
 
+            if (r.clampFragDepth) {
+                transformManager.Add<tint::transform::ClampFragDepth>();
+            }
+
             tint::Program program;
             tint::transform::DataMap transformOutputs;
             {
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.h b/src/dawn/native/vulkan/ShaderModuleVk.h
index b84090e..14bd7ca 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.h
+++ b/src/dawn/native/vulkan/ShaderModuleVk.h
@@ -64,7 +64,8 @@
 
     ResultOrError<ModuleAndSpirv> GetHandleAndSpirv(SingleShaderStage stage,
                                                     const ProgrammableStage& programmableStage,
-                                                    const PipelineLayout* layout);
+                                                    const PipelineLayout* layout,
+                                                    bool clampFragDepth);
 
   private:
     ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 21a1b36..b555260 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -506,6 +506,7 @@
     "end2end/ExperimentalDP4aTests.cpp",
     "end2end/ExternalTextureTests.cpp",
     "end2end/FirstIndexOffsetTests.cpp",
+    "end2end/FragDepthTests.cpp",
     "end2end/GpuMemorySynchronizationTests.cpp",
     "end2end/IndexFormatTests.cpp",
     "end2end/MaxLimitTests.cpp",
diff --git a/src/dawn/tests/end2end/FragDepthTests.cpp b/src/dawn/tests/end2end/FragDepthTests.cpp
new file mode 100644
index 0000000..b320ea6
--- /dev/null
+++ b/src/dawn/tests/end2end/FragDepthTests.cpp
@@ -0,0 +1,231 @@
+// Copyright 2022 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 "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+constexpr wgpu::TextureFormat kDepthFormat = wgpu::TextureFormat::Depth32Float;
+
+class FragDepthTests : public DawnTest {};
+
+// Test that when writing to FragDepth the result is clamped to the viewport.
+TEST_P(FragDepthTests, FragDepthIsClampedToViewport) {
+    // TODO(dawn:1125): Add the shader transform to clamp the frag depth to the GL backend.
+    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
+
+    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+        @vertex fn vs() -> @builtin(position) vec4<f32> {
+            return vec4<f32>(0.0, 0.0, 0.5, 1.0);
+        }
+
+        @fragment fn fs() -> @builtin(frag_depth) f32 {
+            return 1.0;
+        }
+    )");
+
+    // Create the pipeline that uses frag_depth to output the depth.
+    utils::ComboRenderPipelineDescriptor pDesc;
+    pDesc.vertex.module = module;
+    pDesc.vertex.entryPoint = "vs";
+    pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
+    pDesc.cFragment.module = module;
+    pDesc.cFragment.entryPoint = "fs";
+    pDesc.cFragment.targetCount = 0;
+
+    wgpu::DepthStencilState* pDescDS = pDesc.EnableDepthStencil(kDepthFormat);
+    pDescDS->depthWriteEnabled = true;
+    pDescDS->depthCompare = wgpu::CompareFunction::Always;
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);
+
+    // Create a depth-only render pass.
+    wgpu::TextureDescriptor depthDesc;
+    depthDesc.size = {1, 1};
+    depthDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+    depthDesc.format = kDepthFormat;
+    wgpu::Texture depthTexture = device.CreateTexture(&depthDesc);
+
+    utils::ComboRenderPassDescriptor renderPassDesc({}, depthTexture.CreateView());
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+
+    // Draw a point with a skewed viewport, so 1.0 depth gets clamped to 0.5.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+    pass.SetViewport(0, 0, 1, 1, 0.0, 0.5);
+    pass.SetPipeline(pipeline);
+    pass.Draw(1);
+    pass.End();
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_PIXEL_FLOAT_EQ(0.5f, depthTexture, 0, 0);
+}
+
+// Test for the push constant logic for ClampFragDepth in Vulkan to check that changing the
+// pipeline layout doesn't invalidate the push constants that were set.
+TEST_P(FragDepthTests, ChangingPipelineLayoutDoesntInvalidateViewport) {
+    // TODO(dawn:1125): Add the shader transform to clamp the frag depth to the GL backend.
+    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
+
+    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+        @vertex fn vs() -> @builtin(position) vec4<f32> {
+            return vec4<f32>(0.0, 0.0, 0.5, 1.0);
+        }
+
+        @group(0) @binding(0) var<uniform> uniformDepth : f32;
+        @fragment fn fsUniform() -> @builtin(frag_depth) f32 {
+            return uniformDepth;
+        }
+
+        @group(0) @binding(0) var<storage, read> storageDepth : f32;
+        @fragment fn fsStorage() -> @builtin(frag_depth) f32 {
+            return storageDepth;
+        }
+    )");
+
+    // Create the pipeline and bindgroup for the pipeline layout with a uniform buffer.
+    utils::ComboRenderPipelineDescriptor upDesc;
+    upDesc.vertex.module = module;
+    upDesc.vertex.entryPoint = "vs";
+    upDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
+    upDesc.cFragment.module = module;
+    upDesc.cFragment.entryPoint = "fsUniform";
+    upDesc.cFragment.targetCount = 0;
+
+    wgpu::DepthStencilState* upDescDS = upDesc.EnableDepthStencil(kDepthFormat);
+    upDescDS->depthWriteEnabled = true;
+    upDescDS->depthCompare = wgpu::CompareFunction::Always;
+    wgpu::RenderPipeline uniformPipeline = device.CreateRenderPipeline(&upDesc);
+
+    wgpu::Buffer uniformBuffer =
+        utils::CreateBufferFromData<float>(device, wgpu::BufferUsage::Uniform, {0.0});
+    wgpu::BindGroup uniformBG =
+        utils::MakeBindGroup(device, uniformPipeline.GetBindGroupLayout(0), {{0, uniformBuffer}});
+
+    // Create the pipeline and bindgroup for the pipeline layout with a uniform buffer.
+    utils::ComboRenderPipelineDescriptor spDesc;
+    spDesc.vertex.module = module;
+    spDesc.vertex.entryPoint = "vs";
+    spDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
+    spDesc.cFragment.module = module;
+    spDesc.cFragment.entryPoint = "fsStorage";
+    spDesc.cFragment.targetCount = 0;
+
+    wgpu::DepthStencilState* spDescDS = spDesc.EnableDepthStencil(kDepthFormat);
+    spDescDS->depthWriteEnabled = true;
+    spDescDS->depthCompare = wgpu::CompareFunction::Always;
+    wgpu::RenderPipeline storagePipeline = device.CreateRenderPipeline(&spDesc);
+
+    wgpu::Buffer storageBuffer =
+        utils::CreateBufferFromData<float>(device, wgpu::BufferUsage::Storage, {1.0});
+    wgpu::BindGroup storageBG =
+        utils::MakeBindGroup(device, storagePipeline.GetBindGroupLayout(0), {{0, storageBuffer}});
+
+    // Create a depth-only render pass.
+    wgpu::TextureDescriptor depthDesc;
+    depthDesc.size = {1, 1};
+    depthDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+    depthDesc.format = kDepthFormat;
+    wgpu::Texture depthTexture = device.CreateTexture(&depthDesc);
+
+    utils::ComboRenderPassDescriptor renderPassDesc({}, depthTexture.CreateView());
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+
+    // Draw two point with a different pipeline layout to check Vulkan's behavior.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+    pass.SetViewport(0, 0, 1, 1, 0.0, 0.5);
+
+    // Writes 0.0.
+    pass.SetPipeline(uniformPipeline);
+    pass.SetBindGroup(0, uniformBG);
+    pass.Draw(1);
+
+    // Writes 1.0 clamped to 0.5.
+    pass.SetPipeline(storagePipeline);
+    pass.SetBindGroup(0, storageBG);
+    pass.Draw(1);
+
+    pass.End();
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_PIXEL_FLOAT_EQ(0.5f, depthTexture, 0, 0);
+}
+
+// Check that if the fragment is outside of the viewport during rasterization, it is clipped
+// even if it output @builtin(frag_depth).
+TEST_P(FragDepthTests, RasterizationClipBeforeFS) {
+    // TODO(dawn:1616): Metal too needs to clamping of @builtin(frag_depth) to the viewport.
+    DAWN_SUPPRESS_TEST_IF(IsMetal());
+
+    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+        @vertex fn vs() -> @builtin(position) vec4<f32> {
+            return vec4<f32>(0.0, 0.0, 5.0, 1.0);
+        }
+
+        @fragment fn fs() -> @builtin(frag_depth) f32 {
+            return 0.5;
+        }
+    )");
+
+    // Create the pipeline and bindgroup for the pipeline layout with a uniform buffer.
+    utils::ComboRenderPipelineDescriptor pDesc;
+    pDesc.vertex.module = module;
+    pDesc.vertex.entryPoint = "vs";
+    pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
+    pDesc.cFragment.module = module;
+    pDesc.cFragment.entryPoint = "fs";
+    pDesc.cFragment.targetCount = 0;
+
+    wgpu::DepthStencilState* pDescDS = pDesc.EnableDepthStencil(kDepthFormat);
+    pDescDS->depthWriteEnabled = true;
+    pDescDS->depthCompare = wgpu::CompareFunction::Always;
+    wgpu::RenderPipeline uniformPipeline = device.CreateRenderPipeline(&pDesc);
+
+    // Create a depth-only render pass.
+    wgpu::TextureDescriptor depthDesc;
+    depthDesc.size = {1, 1};
+    depthDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+    depthDesc.format = kDepthFormat;
+    wgpu::Texture depthTexture = device.CreateTexture(&depthDesc);
+
+    utils::ComboRenderPassDescriptor renderPassDesc({}, depthTexture.CreateView());
+    renderPassDesc.cDepthStencilAttachmentInfo.depthClearValue = 0.0f;
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+    renderPassDesc.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+
+    // Draw a point with a depth outside of the viewport. It should get discarded.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+    pass.SetPipeline(uniformPipeline);
+    pass.Draw(1);
+    pass.End();
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    // The fragment should be discarded so the depth stayed 0.0, the depthClearValue.
+    EXPECT_PIXEL_FLOAT_EQ(0.0f, depthTexture, 0, 0);
+}
+
+DAWN_INSTANTIATE_TEST(FragDepthTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
diff --git a/webgpu-cts/expectations.txt b/webgpu-cts/expectations.txt
index eba8526..1d7445f 100644
--- a/webgpu-cts/expectations.txt
+++ b/webgpu-cts/expectations.txt
@@ -165,8 +165,7 @@
 ################################################################################
 # depth_clip_clamp failures
 ################################################################################
-crbug.com/dawn/1125 [ ubuntu ] webgpu:api,operation,rendering,depth_clip_clamp:depth_clamp_and_clip:* [ Failure ]
-crbug.com/dawn/1125 [ ubuntu ] webgpu:api,operation,rendering,depth_clip_clamp:depth_test_input_clamped:* [ Failure ]
+crbug.com/dawn/1125 [ mac ] webgpu:api,operation,rendering,depth_clip_clamp:depth_clamp_and_clip:* [ Failure ]
 
 ################################################################################
 # compilation_info failures