From 8e68a89cfb753cae7d1f156a714127d3c8a1de08 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Thu, 5 Jan 2023 12:24:52 +0000 Subject: [PATCH] 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 Reviewed-by: Loko Kung Kokoro: Kokoro Commit-Queue: Corentin Wallez --- include/tint/tint.h | 1 + src/dawn/native/Pipeline.cpp | 4 + src/dawn/native/Pipeline.h | 1 + src/dawn/native/RenderPipeline.cpp | 12 +- src/dawn/native/RenderPipeline.h | 2 + src/dawn/native/ShaderModule.cpp | 1 + src/dawn/native/ShaderModule.h | 1 + src/dawn/native/metal/CommandBufferMTL.mm | 10 +- src/dawn/native/vulkan/AdapterVk.cpp | 4 +- src/dawn/native/vulkan/CommandBufferVk.cpp | 26 +++ src/dawn/native/vulkan/ComputePipelineVk.cpp | 3 +- src/dawn/native/vulkan/DeviceVk.cpp | 1 + src/dawn/native/vulkan/PipelineLayoutVk.cpp | 10 +- src/dawn/native/vulkan/PipelineLayoutVk.h | 11 + src/dawn/native/vulkan/RenderPipelineVk.cpp | 63 +++-- src/dawn/native/vulkan/ShaderModuleVk.cpp | 9 +- src/dawn/native/vulkan/ShaderModuleVk.h | 3 +- src/dawn/tests/BUILD.gn | 1 + src/dawn/tests/end2end/FragDepthTests.cpp | 231 +++++++++++++++++++ webgpu-cts/expectations.txt | 3 +- 20 files changed, 348 insertions(+), 49 deletions(-) create mode 100644 src/dawn/tests/end2end/FragDepthTests.cpp diff --git a/include/tint/tint.h b/include/tint/tint.h index c71682f86a..02ac4231bc 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 581472d216..85a985b5b3 100644 --- a/src/dawn/native/Pipeline.cpp +++ b/src/dawn/native/Pipeline.cpp @@ -216,6 +216,10 @@ const PerStage& PipelineBase::GetAllStages() const { 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 fa0b65bba1..0a88c81459 100644 --- a/src/dawn/native/Pipeline.h +++ b/src/dawn/native/Pipeline.h @@ -59,6 +59,7 @@ class PipelineBase : public ApiObjectBase, public CachedObject { const RequiredBufferSizes& GetMinBufferSizes() const; const ProgrammableStage& GetStage(SingleShaderStage stage) const; const PerStage& GetAllStages() const; + bool HasStage(SingleShaderStage stage) const; wgpu::ShaderStage GetStageMask() const; ResultOrError> GetBindGroupLayout(uint32_t groupIndex); diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp index 60e8125773..1f0b263770 100644 --- a/src/dawn/native/RenderPipeline.cpp +++ b/src/dawn/native/RenderPipeline.cpp @@ -643,6 +643,10 @@ RenderPipelineBase::RenderPipelineBase(DeviceBase* device, } } + if (HasStage(SingleShaderStage::Fragment)) { + mUsesFragDepth = GetStage(SingleShaderStage::Fragment).metadata->usesFragDepth; + } + SetContentHash(ComputeContentHash()); GetObjectTrackingList()->Track(this); @@ -829,22 +833,24 @@ bool RenderPipelineBase::IsAlphaToCoverageEnabled() const { 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 afba4a7590..a1eaee6e8d 100644 --- a/src/dawn/native/RenderPipeline.h +++ b/src/dawn/native/RenderPipeline.h @@ -101,6 +101,7 @@ class RenderPipelineBase : public PipelineBase { bool IsAlphaToCoverageEnabled() const; bool WritesDepth() const; bool WritesStencil() const; + bool UsesFragDepth() const; const AttachmentState* GetAttachmentState() const; @@ -140,6 +141,7 @@ class RenderPipelineBase : public PipelineBase { 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 812980d50e..8a6c4e1df8 100644 --- a/src/dawn/native/ShaderModule.cpp +++ b/src/dawn/native/ShaderModule.cpp @@ -646,6 +646,7 @@ ResultOrError> ReflectEntryPointUsingTint( 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 af63e850fd..5612d1571c 100644 --- a/src/dawn/native/ShaderModule.h +++ b/src/dawn/native/ShaderModule.h @@ -245,6 +245,7 @@ struct EntryPointMetadata { std::unordered_set 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 f1f5131eba..397591ac5f 100644 --- a/src/dawn/native/metal/CommandBufferMTL.mm +++ b/src/dawn/native/metal/CommandBufferMTL.mm @@ -1490,9 +1490,13 @@ MaybeError CommandBuffer::EncodeRenderPass(id encoder, 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 be468e8b95..7bcce2a05b 100644 --- a/src/dawn/native/vulkan/AdapterVk.cpp +++ b/src/dawn/native/vulkan/AdapterVk.cpp @@ -232,7 +232,9 @@ MaybeError Adapter::InitializeSupportedFeaturesImpl() { 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 ef88a92f78..b67b0df035 100644 --- a/src/dawn/native/vulkan/CommandBufferVk.cpp +++ b/src/dawn/native/vulkan/CommandBufferVk.cpp @@ -1108,6 +1108,23 @@ MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* recordingCon 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 @@ MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* recordingCon lastPipeline = pipeline; descriptorSets.OnSetPipeline(pipeline); + + // Apply the deferred min/maxDepth push constants update if needed. + ApplyClampFragDepthArgs(); break; } @@ -1302,6 +1322,12 @@ MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* recordingCon } 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 49522e2aa1..f607f7b4ad 100644 --- a/src/dawn/native/vulkan/ComputePipelineVk.cpp +++ b/src/dawn/native/vulkan/ComputePipelineVk.cpp @@ -61,7 +61,8 @@ MaybeError ComputePipeline::Initialize() { 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 7a7b61ec15..c71ce86955 100644 --- a/src/dawn/native/vulkan/DeviceVk.cpp +++ b/src/dawn/native/vulkan/DeviceVk.cpp @@ -505,6 +505,7 @@ ResultOrError Device::CreateDevice(VkPhysicalDevice physicalD 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 a47c4eda27..69b0d23bdd 100644 --- a/src/dawn/native/vulkan/PipelineLayoutVk.cpp +++ b/src/dawn/native/vulkan/PipelineLayoutVk.cpp @@ -46,14 +46,20 @@ MaybeError PipelineLayout::Initialize() { 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 2b8f5cf72a..ede3aa3574 100644 --- a/src/dawn/native/vulkan/PipelineLayoutVk.h +++ b/src/dawn/native/vulkan/PipelineLayoutVk.h @@ -24,6 +24,17 @@ namespace dawn::native::vulkan { 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> Create(Device* device, diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp index 0ca94216d0..5595bf0c80 100644 --- a/src/dawn/native/vulkan/RenderPipelineVk.cpp +++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp @@ -343,45 +343,38 @@ MaybeError RenderPipeline::Initialize() { std::array 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 @@ MaybeError RenderPipeline::Initialize() { 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 4966e921f1..39fd0dfada 100644 --- a/src/dawn/native/vulkan/ShaderModuleVk.cpp +++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp @@ -175,6 +175,7 @@ ShaderModule::~ShaderModule() = default; X(bool, disableWorkgroupInit) \ X(bool, disableSymbolRenaming) \ X(bool, useZeroInitializeWorkgroupMemoryExtension) \ + X(bool, clampFragDepth) \ X(CacheKey::UnsafeUnkeyedValue, tracePlatform) DAWN_MAKE_CACHE_REQUEST(SpirvCompilationRequest, SPIRV_COMPILATION_REQUEST_MEMBERS); @@ -183,7 +184,8 @@ DAWN_MAKE_CACHE_REQUEST(SpirvCompilationRequest, SPIRV_COMPILATION_REQUEST_MEMBE ResultOrError 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 @@ ResultOrError ShaderModule::GetHandleAndSpirv( 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 @@ ResultOrError ShaderModule::GetHandleAndSpirv( std::move(r.substituteOverrideConfig).value()); } + if (r.clampFragDepth) { + transformManager.Add(); + } + tint::Program program; tint::transform::DataMap transformOutputs; { diff --git a/src/dawn/native/vulkan/ShaderModuleVk.h b/src/dawn/native/vulkan/ShaderModuleVk.h index b84090e993..14bd7ca3a1 100644 --- a/src/dawn/native/vulkan/ShaderModuleVk.h +++ b/src/dawn/native/vulkan/ShaderModuleVk.h @@ -64,7 +64,8 @@ class ShaderModule final : public ShaderModuleBase { ResultOrError 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 21a1b3684e..b555260be5 100644 --- a/src/dawn/tests/BUILD.gn +++ b/src/dawn/tests/BUILD.gn @@ -506,6 +506,7 @@ source_set("end2end_tests_sources") { "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 0000000000..b320ea665f --- /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 { + return vec4(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 { + return vec4(0.0, 0.0, 0.5, 1.0); + } + + @group(0) @binding(0) var uniformDepth : f32; + @fragment fn fsUniform() -> @builtin(frag_depth) f32 { + return uniformDepth; + } + + @group(0) @binding(0) var 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(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(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 { + return vec4(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 eba8526f85..1d7445f389 100644 --- a/webgpu-cts/expectations.txt +++ b/webgpu-cts/expectations.txt @@ -165,8 +165,7 @@ crbug.com/dawn/0000 webgpu:util,texture,texel_data:unorm_texel_data_in_shader:fo ################################################################################ # 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