| // Copyright 2021 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <vector> |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| #include "partition_alloc/pointers/raw_ptr.h" |
| |
| namespace dawn { |
| namespace { |
| |
| constexpr static unsigned int kRTSize = 1; |
| |
| class DepthClippingTest : public DawnTest { |
| protected: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DepthClipControl})); |
| |
| wgpu::TextureDescriptor renderTargetDescriptor; |
| renderTargetDescriptor.size = {kRTSize, kRTSize}; |
| renderTargetDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; |
| renderTargetDescriptor.usage = |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| renderTarget = device.CreateTexture(&renderTargetDescriptor); |
| |
| renderTargetView = renderTarget.CreateView(); |
| |
| wgpu::TextureDescriptor depthDescriptor; |
| depthDescriptor.dimension = wgpu::TextureDimension::e2D; |
| depthDescriptor.size = {kRTSize, kRTSize}; |
| depthDescriptor.format = wgpu::TextureFormat::Depth24PlusStencil8; |
| depthDescriptor.usage = wgpu::TextureUsage::RenderAttachment; |
| depthTexture = device.CreateTexture(&depthDescriptor); |
| |
| depthTextureView = depthTexture.CreateView(); |
| |
| vsModule = utils::CreateShaderModule(device, R"( |
| struct UBO { |
| color : vec3f, |
| depth : f32, |
| } |
| @group(0) @binding(0) var<uniform> ubo : UBO; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(0.0, 0.0, ubo.depth, 1.0); |
| })"); |
| |
| fsModule = utils::CreateShaderModule(device, R"( |
| struct UBO { |
| color : vec3f, |
| depth : f32, |
| } |
| @group(0) @binding(0) var<uniform> ubo : UBO; |
| |
| @fragment fn main() -> @location(0) vec4f { |
| return vec4f(ubo.color, 1.0); |
| })"); |
| } |
| |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| std::vector<wgpu::FeatureName> requiredFeatures = {}; |
| if (SupportsFeatures({wgpu::FeatureName::DepthClipControl})) { |
| requiredFeatures.push_back(wgpu::FeatureName::DepthClipControl); |
| } |
| return requiredFeatures; |
| } |
| |
| struct TestSpec { |
| raw_ptr<wgpu::PrimitiveDepthClipControl> depthClipControl; |
| utils::RGBA8 color; |
| float depth; |
| }; |
| |
| // Each test param represents a pair of triangles with a color, depth, stencil value, and |
| // depthStencil state, one frontfacing, one backfacing Draw the triangles in order and check the |
| // expected colors for the frontfaces and backfaces |
| void DoTest(const std::vector<TestSpec>& testParams, const utils::RGBA8& expected) { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| |
| struct TriangleData { |
| float color[3]; |
| float depth; |
| }; |
| |
| utils::ComboRenderPassDescriptor renderPass({renderTargetView}, depthTextureView); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| |
| for (size_t i = 0; i < testParams.size(); ++i) { |
| const TestSpec& test = testParams[i]; |
| |
| TriangleData data = { |
| {static_cast<float>(test.color.r) / 255.f, static_cast<float>(test.color.g) / 255.f, |
| static_cast<float>(test.color.b) / 255.f}, |
| test.depth, |
| }; |
| // Upload a buffer for each triangle's depth and color data |
| wgpu::Buffer buffer = utils::CreateBufferFromData(device, &data, sizeof(TriangleData), |
| wgpu::BufferUsage::Uniform); |
| |
| // Create a pipeline for the triangles with the test spec's params. |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.primitive.nextInChain = test.depthClipControl; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil(); |
| depthStencil->depthWriteEnabled = true; |
| depthStencil->format = wgpu::TextureFormat::Depth24PlusStencil8; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| // Create a bind group for the data |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.Draw(1); |
| } |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(expected, renderTarget, 0, 0) << "Pixel check failed"; |
| } |
| |
| wgpu::Texture renderTarget; |
| wgpu::Texture depthTexture; |
| wgpu::TextureView renderTargetView; |
| wgpu::TextureView depthTextureView; |
| wgpu::ShaderModule vsModule; |
| wgpu::ShaderModule fsModule; |
| }; |
| |
| // Test that fragments beyond the far plane are not clipped if unclippedDepth is true |
| TEST_P(DepthClippingTest, UnclippedBeyondFarPlane) { |
| wgpu::PrimitiveDepthClipControl depthClipControl; |
| depthClipControl.unclippedDepth = true; |
| |
| DoTest( |
| { |
| // Draw a red triangle at depth 1. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 1.f, /* depth */ |
| }, |
| // Draw a green triangle at depth 2 which should not be clipped. |
| { |
| &depthClipControl, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| 2.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be green even though the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(0, 255, 0, 255)); |
| } |
| |
| // Test that fragments beyond the far plane are clipped if unclippedDepth is false |
| TEST_P(DepthClippingTest, ClippedBeyondFarPlane) { |
| wgpu::PrimitiveDepthClipControl depthClipControl; |
| depthClipControl.unclippedDepth = false; |
| |
| DoTest( |
| { |
| // Draw a red triangle at depth 1. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 1.f, /* depth */ |
| }, |
| // Draw a green triangle at depth 2 which should be clipped. |
| { |
| &depthClipControl, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| 2.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be red since the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(255, 0, 0, 255)); |
| } |
| |
| // Test that fragments beyond the far plane are clipped if unclippedDepth is not specified |
| TEST_P(DepthClippingTest, ClippedBeyondFarPlaneFeatureUnused) { |
| DoTest( |
| { |
| // Draw a red triangle at depth 1. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 1.f, /* depth */ |
| }, |
| // Draw a green triangle at depth 2 which should be clipped. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| 2.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be red since the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(255, 0, 0, 255)); |
| } |
| |
| // Test that fragments beyond the near plane are not clipped if unclippedDepth is true |
| TEST_P(DepthClippingTest, UnclippedBeyondNearPlane) { |
| wgpu::PrimitiveDepthClipControl depthClipControl; |
| depthClipControl.unclippedDepth = true; |
| |
| DoTest( |
| { |
| // Draw a red triangle at depth 0. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 0.f, /* depth */ |
| }, |
| // Draw a green triangle at depth -1 which should not be clipped. |
| { |
| &depthClipControl, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| -1.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be green even though the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(0, 255, 0, 255)); |
| } |
| |
| // Test that fragments beyond the near plane are clipped if unclippedDepth is false |
| TEST_P(DepthClippingTest, ClippedBeyondNearPlane) { |
| wgpu::PrimitiveDepthClipControl depthClipControl; |
| depthClipControl.unclippedDepth = false; |
| |
| DoTest( |
| { |
| // Draw a red triangle at depth 0. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 0.f, /* depth */ |
| }, |
| // Draw a green triangle at depth -1 which should be clipped. |
| { |
| &depthClipControl, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| -1.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be red because the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(255, 0, 0, 255)); |
| } |
| |
| // Test that fragments beyond the near plane are clipped if unclippedDepth is not specified |
| TEST_P(DepthClippingTest, ClippedBeyondNearPlaneFeatureUnused) { |
| DoTest( |
| { |
| // Draw a red triangle at depth 0. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(255, 0, 0, 255), /* color */ |
| 0.f, /* depth */ |
| }, |
| // Draw a green triangle at depth -1 which should be clipped. |
| { |
| nullptr, /* depthClipControl */ |
| utils::RGBA8(0, 255, 0, 255), /* color */ |
| -1.f, /* depth */ |
| }, |
| }, |
| // The resulting fragment should be red because the green triangle is |
| // outside the clip volume. |
| utils::RGBA8(255, 0, 0, 255)); |
| } |
| |
| // Test that fragments are properly clipped or clamped if multiple render pipelines are used |
| // within the same render pass with differing unclippedDepth values. |
| TEST_P(DepthClippingTest, MultipleRenderPipelines) { |
| wgpu::PrimitiveDepthClipControl depthClipControl1; |
| depthClipControl1.unclippedDepth = true; |
| |
| wgpu::PrimitiveDepthClipControl depthClipControl2; |
| depthClipControl2.unclippedDepth = false; |
| |
| DoTest( |
| { |
| // Draw green with no clipping |
| { |
| &depthClipControl1, utils::RGBA8(0, 255, 0, 255), /* color */ |
| 2.f, /* depth */ |
| }, |
| // Draw red with clipping |
| { |
| &depthClipControl2, utils::RGBA8(255, 0, 0, 255), /* color */ |
| 2.f, /* depth */ |
| }, |
| }, |
| utils::RGBA8(0, 255, 0, 255)); // Result should be green |
| } |
| |
| // Test that fragments are not clipped if unclippedDepth is true and that their |
| // depths are not being clamped instead. In the fragment shader, we should see |
| // depth values outside the viewport. |
| TEST_P(DepthClippingTest, UnclippedNotClamped) { |
| wgpu::PrimitiveDepthClipControl depthClipControl; |
| depthClipControl.unclippedDepth = true; |
| |
| // Create a pipeline to render a point. |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.primitive.nextInChain = &depthClipControl; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| // Draw the point at (0, 0) with depth 2.0. |
| descriptor.vertex.module = utils::CreateShaderModule(device, R"( |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(0.0, 0.0, 2.0, 1.0); |
| })"); |
| // Write frag_pos.z / 4.0 which should be about 0.5 to the red channel. |
| // This is the depth output from the vertex shader which is not clamped to the viewport. |
| descriptor.cFragment.module = utils::CreateShaderModule(device, R"( |
| @fragment fn main(@builtin(position) frag_pos: vec4f) -> @location(0) vec4f { |
| return vec4f(frag_pos.z / 4.0, 0.0, 0.0, 1.0); |
| })"); |
| wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil(); |
| depthStencil->depthWriteEnabled = true; |
| depthStencil->format = wgpu::TextureFormat::Depth24PlusStencil8; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| // Draw the point. |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| utils::ComboRenderPassDescriptor renderPass({renderTargetView}, depthTextureView); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| pass.SetPipeline(pipeline); |
| pass.Draw(1); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_BETWEEN(utils::RGBA8(127, 0, 0, 255), utils::RGBA8(128, 0, 0, 255), |
| renderTarget, 0, 0) |
| << "Pixel check failed"; |
| } |
| |
| DAWN_INSTANTIATE_TEST(DepthClippingTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| VulkanBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |