Implement Culling and FrontFace

This patch implements Culling and FrontFace on backends, and add tests too.

This test also verified that we couldn't invert FrontFace on Metal backend.
Otherwise, the tests would fail on all HWs.

But we do need to invert CCW/CW on OpenGL backend. Because Y axis is up in
OpenGL while Y axis is down in WebGPU.

Bug=dawn:43

Change-Id: I7dd0922477397a13c5f7208e104ff352a673a556
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/8420
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
index 9bac495..9cd2666 100644
--- a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
+++ b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp
@@ -138,6 +138,19 @@
             }
         }
 
+        D3D12_CULL_MODE D3D12CullMode(dawn::CullMode mode) {
+            switch (mode) {
+                case dawn::CullMode::None:
+                    return D3D12_CULL_MODE_NONE;
+                case dawn::CullMode::Front:
+                    return D3D12_CULL_MODE_FRONT;
+                case dawn::CullMode::Back:
+                    return D3D12_CULL_MODE_BACK;
+                default:
+                    UNREACHABLE();
+            }
+        }
+
         D3D12_BLEND D3D12Blend(dawn::BlendFactor factor) {
             switch (factor) {
                 case dawn::BlendFactor::Zero:
@@ -343,8 +356,9 @@
         }
 
         descriptorD3D12.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
-        descriptorD3D12.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
-        descriptorD3D12.RasterizerState.FrontCounterClockwise = FALSE;
+        descriptorD3D12.RasterizerState.CullMode = D3D12CullMode(GetCullMode());
+        descriptorD3D12.RasterizerState.FrontCounterClockwise =
+            (GetFrontFace() == dawn::FrontFace::CCW) ? TRUE : FALSE;
         descriptorD3D12.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
         descriptorD3D12.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
         descriptorD3D12.RasterizerState.SlopeScaledDepthBias =
diff --git a/src/dawn_native/metal/RenderPipelineMTL.mm b/src/dawn_native/metal/RenderPipelineMTL.mm
index 7d7fa53..1a9e106 100644
--- a/src/dawn_native/metal/RenderPipelineMTL.mm
+++ b/src/dawn_native/metal/RenderPipelineMTL.mm
@@ -283,12 +283,11 @@
         }
 
         MTLWinding MTLFrontFace(dawn::FrontFace face) {
-            // Note that these are inverted because we flip the Y coordinate in the vertex shader
             switch (face) {
                 case dawn::FrontFace::CW:
-                    return MTLWindingCounterClockwise;
-                case dawn::FrontFace::CCW:
                     return MTLWindingClockwise;
+                case dawn::FrontFace::CCW:
+                    return MTLWindingCounterClockwise;
             }
         }
 
diff --git a/src/dawn_native/opengl/RenderPipelineGL.cpp b/src/dawn_native/opengl/RenderPipelineGL.cpp
index 037f185..59b1e26 100644
--- a/src/dawn_native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn_native/opengl/RenderPipelineGL.cpp
@@ -40,6 +40,23 @@
             }
         }
 
+        void ApplyFrontFaceAndCulling(const OpenGLFunctions& gl,
+                                      dawn::FrontFace face,
+                                      dawn::CullMode mode) {
+            if (mode == dawn::CullMode::None) {
+                gl.Disable(GL_CULL_FACE);
+            } else {
+                gl.Enable(GL_CULL_FACE);
+                // Note that we invert winding direction in OpenGL. Because Y axis is up in OpenGL,
+                // which is different from WebGPU and other backends (Y axis is down).
+                GLenum direction = (face == dawn::FrontFace::CCW) ? GL_CW : GL_CCW;
+                gl.FrontFace(direction);
+
+                GLenum cullMode = (mode == dawn::CullMode::Front) ? GL_FRONT : GL_BACK;
+                gl.CullFace(cullMode);
+            }
+        }
+
         GLenum GLBlendFactor(dawn::BlendFactor factor, bool alpha) {
             switch (factor) {
                 case dawn::BlendFactor::Zero:
@@ -236,6 +253,8 @@
         ASSERT(mVertexArrayObject);
         gl.BindVertexArray(mVertexArrayObject);
 
+        ApplyFrontFaceAndCulling(gl, GetFrontFace(), GetCullMode());
+
         ApplyDepthStencilState(gl, GetDepthStencilStateDescriptor(), &persistentPipelineState);
 
         for (uint32_t attachmentSlot : IterateBitSet(GetColorAttachmentsMask())) {
diff --git a/src/dawn_native/vulkan/RenderPipelineVk.cpp b/src/dawn_native/vulkan/RenderPipelineVk.cpp
index e35ea67..7eb164f 100644
--- a/src/dawn_native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn_native/vulkan/RenderPipelineVk.cpp
@@ -137,6 +137,26 @@
             }
         }
 
+        VkFrontFace VulkanFrontFace(dawn::FrontFace face) {
+            switch (face) {
+                case dawn::FrontFace::CCW:
+                    return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+                case dawn::FrontFace::CW:
+                    return VK_FRONT_FACE_CLOCKWISE;
+            }
+        }
+
+        VkCullModeFlagBits VulkanCullMode(dawn::CullMode mode) {
+            switch (mode) {
+                case dawn::CullMode::None:
+                    return VK_CULL_MODE_NONE;
+                case dawn::CullMode::Front:
+                    return VK_CULL_MODE_FRONT_BIT;
+                case dawn::CullMode::Back:
+                    return VK_CULL_MODE_BACK_BIT;
+            }
+        }
+
         VkBlendFactor VulkanBlendFactor(dawn::BlendFactor factor) {
             switch (factor) {
                 case dawn::BlendFactor::Zero:
@@ -355,8 +375,8 @@
         rasterization.depthClampEnable = VK_FALSE;
         rasterization.rasterizerDiscardEnable = VK_FALSE;
         rasterization.polygonMode = VK_POLYGON_MODE_FILL;
-        rasterization.cullMode = VK_CULL_MODE_NONE;
-        rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+        rasterization.cullMode = VulkanCullMode(GetCullMode());
+        rasterization.frontFace = VulkanFrontFace(GetFrontFace());
         rasterization.depthBiasEnable = VK_FALSE;
         rasterization.depthBiasConstantFactor = 0.0f;
         rasterization.depthBiasClamp = 0.0f;
diff --git a/src/tests/end2end/CullingTests.cpp b/src/tests/end2end/CullingTests.cpp
new file mode 100644
index 0000000..a9fc6aca9
--- /dev/null
+++ b/src/tests/end2end/CullingTests.cpp
@@ -0,0 +1,127 @@
+// Copyright 2019 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 "tests/DawnTest.h"
+
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/DawnHelpers.h"
+
+class CullingTest : public DawnTest {
+  protected:
+    dawn::RenderPipeline CreatePipelineForTest(dawn::FrontFace frontFace, dawn::CullMode cullMode) {
+        utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
+
+        // Draw two triangles with different winding orders:
+        // 1. The top-left one is counterclockwise (CCW)
+        // 2. The bottom-right one is clockwise (CW)
+        const char* vs =
+            R"(#version 450
+        const vec2 pos[6] = vec2[6](vec2(-1.0f, -1.0f),
+                                    vec2(-1.0f,  0.0f),
+                                    vec2( 0.0f, -1.0f),
+                                    vec2( 0.0f,  1.0f),
+                                    vec2( 1.0f,  0.0f),
+                                    vec2( 1.0f,  1.0f));
+        void main() {
+           gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
+       })";
+        pipelineDescriptor.cVertexStage.module =
+            utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, vs);
+
+        const char* fs =
+            "#version 450\n"
+            "layout(location = 0) out vec4 fragColor;"
+            "void main() {\n"
+            "   fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+            "}\n";
+        pipelineDescriptor.cFragmentStage.module =
+            utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, fs);
+
+        // Set culling mode and front face according to the parameters
+        pipelineDescriptor.cRasterizationState.frontFace = frontFace;
+        pipelineDescriptor.cRasterizationState.cullMode = cullMode;
+
+        return device.CreateRenderPipeline(&pipelineDescriptor);
+    }
+
+    dawn::Texture Create2DTextureForTest(dawn::TextureFormat format) {
+        dawn::TextureDescriptor textureDescriptor;
+        textureDescriptor.dimension = dawn::TextureDimension::e2D;
+        textureDescriptor.format = format;
+        textureDescriptor.usage =
+            dawn::TextureUsageBit::OutputAttachment | dawn::TextureUsageBit::TransferSrc;
+        textureDescriptor.arrayLayerCount = 1;
+        textureDescriptor.mipLevelCount = 1;
+        textureDescriptor.sampleCount = 1;
+        textureDescriptor.size = {kSize, kSize, 1};
+        return device.CreateTexture(&textureDescriptor);
+    }
+
+    void DoTest(dawn::FrontFace frontFace,
+                dawn::CullMode cullMode,
+                bool isCCWTriangleCulled,
+                bool isCWTriangleCulled) {
+        dawn::Texture colorTexture = Create2DTextureForTest(dawn::TextureFormat::RGBA8Unorm);
+
+        utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateDefaultView()});
+        renderPassDescriptor.cColorAttachmentsInfoPtr[0]->clearColor = {0.0, 1.0, 0.0, 1.0};
+        renderPassDescriptor.cColorAttachmentsInfoPtr[0]->loadOp = dawn::LoadOp::Clear;
+
+        dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        dawn::RenderPassEncoder renderPass = commandEncoder.BeginRenderPass(&renderPassDescriptor);
+        renderPass.SetPipeline(CreatePipelineForTest(frontFace, cullMode));
+        renderPass.Draw(6, 1, 0, 0);
+        renderPass.EndPass();
+        dawn::CommandBuffer commandBuffer = commandEncoder.Finish();
+        dawn::Queue queue = device.CreateQueue();
+        queue.Submit(1, &commandBuffer);
+
+        constexpr RGBA8 kDrawingColor = RGBA8(255, 0, 0, 255);
+        constexpr RGBA8 kBackgroundColor = RGBA8(0, 255, 0, 255);
+
+        RGBA8 kCCWTriangleColor = isCCWTriangleCulled ? kBackgroundColor : kDrawingColor;
+        EXPECT_PIXEL_RGBA8_EQ(kCCWTriangleColor, colorTexture, 0, 0);
+
+        RGBA8 kCWTriangleColor = isCWTriangleCulled ? kBackgroundColor : kDrawingColor;
+        EXPECT_PIXEL_RGBA8_EQ(kCWTriangleColor, colorTexture, kSize - 1, kSize - 1);
+    }
+
+    static constexpr uint32_t kSize = 4;
+};
+
+TEST_P(CullingTest, CullNoneWhenCCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CCW, dawn::CullMode::None, false, false);
+}
+
+TEST_P(CullingTest, CullFrontFaceWhenCCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CCW, dawn::CullMode::Front, true, false);
+}
+
+TEST_P(CullingTest, CullBackFaceWhenCCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CCW, dawn::CullMode::Back, false, true);
+}
+
+TEST_P(CullingTest, CullNoneWhenCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CW, dawn::CullMode::None, false, false);
+}
+
+TEST_P(CullingTest, CullFrontFaceWhenCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CW, dawn::CullMode::Front, false, true);
+}
+
+TEST_P(CullingTest, CullBackFaceWhenCWIsFrontFace) {
+    DoTest(dawn::FrontFace::CW, dawn::CullMode::Back, true, false);
+}
+
+DAWN_INSTANTIATE_TEST(CullingTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);