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