Accept StencilFaceState with Undefined when there is not stencil.

The TrivialFrontendDefaults happen after validation in this case so
Undefined value must be handled explicitly. Also moves UsesStencil as a
getter on the RenderPipeline instead of a freestanding function.

Fixed: dawn:2475
Change-Id: Ibaf74421a1ffc33b05088efcc1092f7aa6ddf66c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/183241
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index eff2434..212180c 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -273,6 +273,26 @@
     return {};
 }
 
+MaybeError ValidateStencilFaceUnused(StencilFaceState face) {
+    DAWN_INVALID_IF((face.compare != wgpu::CompareFunction::Always) &&
+                        (face.compare != wgpu::CompareFunction::Undefined),
+                    "compare (%s) is defined and not %s.", face.compare,
+                    wgpu::CompareFunction::Always);
+    DAWN_INVALID_IF((face.failOp != wgpu::StencilOperation::Keep) &&
+                        (face.failOp != wgpu::StencilOperation::Undefined),
+                    "failOp (%s) is defined and not %s.", face.failOp,
+                    wgpu::StencilOperation::Keep);
+    DAWN_INVALID_IF((face.depthFailOp != wgpu::StencilOperation::Keep) &&
+                        (face.depthFailOp != wgpu::StencilOperation::Undefined),
+                    "depthFailOp (%s) is defined and not %s.", face.depthFailOp,
+                    wgpu::StencilOperation::Keep);
+    DAWN_INVALID_IF((face.passOp != wgpu::StencilOperation::Keep) &&
+                        (face.passOp != wgpu::StencilOperation::Undefined),
+                    "passOp (%s) is defined and not %s.", face.passOp,
+                    wgpu::StencilOperation::Keep);
+    return {};
+}
+
 MaybeError ValidateDepthStencilState(const DeviceBase* device,
                                      const DepthStencilState* descriptor) {
     DAWN_TRY_CONTEXT(ValidateCompareFunction(descriptor->depthCompare),
@@ -342,10 +362,16 @@
         "Depth stencil format (%s) doesn't have depth aspect while depthWriteEnabled (%u) is true.",
         descriptor->format, descriptor->depthWriteEnabled);
 
-    DAWN_INVALID_IF(!format->HasStencil() && StencilTestEnabled(descriptor),
-                    "Depth stencil format (%s) doesn't have stencil aspect while stencil "
-                    "test or stencil write is enabled.",
-                    descriptor->format);
+    if (!format->HasStencil()) {
+        DAWN_TRY_CONTEXT(ValidateStencilFaceUnused(descriptor->stencilFront),
+                         "validating that stencilFront doesn't use stencil when the depth-stencil "
+                         "format (%s) doesn't have a stencil aspect.",
+                         descriptor->format);
+        DAWN_TRY_CONTEXT(ValidateStencilFaceUnused(descriptor->stencilBack),
+                         "validating that stencilBack doesn't use stencil when the depth-stencil "
+                         "format (%s) doesn't have a stencil aspect.",
+                         descriptor->format);
+    }
 
     return {};
 }
@@ -840,17 +866,6 @@
     return stages;
 }
 
-bool StencilTestEnabled(const DepthStencilState* depthStencil) {
-    return depthStencil->stencilBack.compare != wgpu::CompareFunction::Always ||
-           depthStencil->stencilBack.failOp != wgpu::StencilOperation::Keep ||
-           depthStencil->stencilBack.depthFailOp != wgpu::StencilOperation::Keep ||
-           depthStencil->stencilBack.passOp != wgpu::StencilOperation::Keep ||
-           depthStencil->stencilFront.compare != wgpu::CompareFunction::Always ||
-           depthStencil->stencilFront.failOp != wgpu::StencilOperation::Keep ||
-           depthStencil->stencilFront.depthFailOp != wgpu::StencilOperation::Keep ||
-           depthStencil->stencilFront.passOp != wgpu::StencilOperation::Keep;
-}
-
 // RenderPipelineBase
 
 RenderPipelineBase::RenderPipelineBase(DeviceBase* device,
@@ -1078,6 +1093,17 @@
     return &mDepthStencil;
 }
 
+bool RenderPipelineBase::UsesStencil() const {
+    return mDepthStencil.stencilBack.compare != wgpu::CompareFunction::Always ||
+           mDepthStencil.stencilBack.failOp != wgpu::StencilOperation::Keep ||
+           mDepthStencil.stencilBack.depthFailOp != wgpu::StencilOperation::Keep ||
+           mDepthStencil.stencilBack.passOp != wgpu::StencilOperation::Keep ||
+           mDepthStencil.stencilFront.compare != wgpu::CompareFunction::Always ||
+           mDepthStencil.stencilFront.failOp != wgpu::StencilOperation::Keep ||
+           mDepthStencil.stencilFront.depthFailOp != wgpu::StencilOperation::Keep ||
+           mDepthStencil.stencilFront.passOp != wgpu::StencilOperation::Keep;
+}
+
 wgpu::PrimitiveTopology RenderPipelineBase::GetPrimitiveTopology() const {
     DAWN_ASSERT(!IsError());
     return mPrimitive.topology;
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index 987eae5..3a460cf 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -70,8 +70,6 @@
 
 bool IsStripPrimitiveTopology(wgpu::PrimitiveTopology primitiveTopology);
 
-bool StencilTestEnabled(const DepthStencilState* depthStencil);
-
 struct VertexAttributeInfo {
     wgpu::VertexFormat format;
     uint64_t offset;
@@ -98,6 +96,7 @@
 
     ObjectType GetType() const override;
 
+    // Vertex getters
     const VertexAttributeMask& GetAttributeLocationsUsed() const;
     const VertexAttributeInfo& GetAttribute(VertexAttributeLocation location) const;
     const VertexBufferMask& GetVertexBuffersUsed() const;
@@ -106,25 +105,34 @@
     const VertexBufferInfo& GetVertexBuffer(VertexBufferSlot slot) const;
     uint32_t GetVertexBufferCount() const;
 
+    // Color attachment getters
     const ColorTargetState* GetColorTargetState(ColorAttachmentIndex attachmentSlot) const;
-    const DepthStencilState* GetDepthStencilState() const;
+    ColorAttachmentMask GetColorAttachmentsMask() const;
+    wgpu::TextureFormat GetColorAttachmentFormat(ColorAttachmentIndex attachment) const;
+
+    // Primitive getters
     wgpu::PrimitiveTopology GetPrimitiveTopology() const;
     wgpu::IndexFormat GetStripIndexFormat() const;
     wgpu::CullMode GetCullMode() const;
     wgpu::FrontFace GetFrontFace() const;
+
+    // Depth-stencil getters
+    const DepthStencilState* GetDepthStencilState() const;
+    bool HasDepthStencilAttachment() const;
+    bool UsesStencil() const;
+    wgpu::TextureFormat GetDepthStencilFormat() const;
     bool IsDepthBiasEnabled() const;
     int32_t GetDepthBias() const;
     float GetDepthBiasSlopeScale() const;
     float GetDepthBiasClamp() const;
     bool HasUnclippedDepth() const;
 
-    ColorAttachmentMask GetColorAttachmentsMask() const;
-    bool HasDepthStencilAttachment() const;
-    wgpu::TextureFormat GetColorAttachmentFormat(ColorAttachmentIndex attachment) const;
-    wgpu::TextureFormat GetDepthStencilFormat() const;
+    // Multisample getters
     uint32_t GetSampleCount() const;
     uint32_t GetSampleMask() const;
     bool IsAlphaToCoverageEnabled() const;
+
+    // Shader builtin getters
     bool WritesDepth() const;
     bool WritesStencil() const;
     bool UsesFragDepth() const;
diff --git a/src/dawn/native/d3d11/RenderPipelineD3D11.cpp b/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
index 69299d0..03cc7be 100644
--- a/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
+++ b/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
@@ -412,7 +412,7 @@
         state->depthWriteEnabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
     depthStencilDesc.DepthFunc = ToD3D11ComparisonFunc(state->depthCompare);
 
-    depthStencilDesc.StencilEnable = StencilTestEnabled(state) ? TRUE : FALSE;
+    depthStencilDesc.StencilEnable = UsesStencil() ? TRUE : FALSE;
     depthStencilDesc.StencilReadMask = static_cast<UINT8>(state->stencilReadMask);
     depthStencilDesc.StencilWriteMask = static_cast<UINT8>(state->stencilWriteMask);
 
diff --git a/src/dawn/native/d3d12/RenderPipelineD3D12.cpp b/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
index 3efdd1d..9c71063 100644
--- a/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
+++ b/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
@@ -288,26 +288,6 @@
     return desc;
 }
 
-D3D12_DEPTH_STENCIL_DESC ComputeDepthStencilDesc(const DepthStencilState* descriptor) {
-    D3D12_DEPTH_STENCIL_DESC depthStencilDescriptor = {};
-    depthStencilDescriptor.DepthEnable =
-        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
-         !descriptor->depthWriteEnabled)
-            ? FALSE
-            : TRUE;
-    depthStencilDescriptor.DepthWriteMask =
-        descriptor->depthWriteEnabled ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
-    depthStencilDescriptor.DepthFunc = ToD3D12ComparisonFunc(descriptor->depthCompare);
-
-    depthStencilDescriptor.StencilEnable = StencilTestEnabled(descriptor) ? TRUE : FALSE;
-    depthStencilDescriptor.StencilReadMask = static_cast<UINT8>(descriptor->stencilReadMask);
-    depthStencilDescriptor.StencilWriteMask = static_cast<UINT8>(descriptor->stencilWriteMask);
-
-    depthStencilDescriptor.FrontFace = StencilOpDesc(descriptor->stencilFront);
-    depthStencilDescriptor.BackFace = StencilOpDesc(descriptor->stencilBack);
-    return depthStencilDescriptor;
-}
-
 D3D12_INDEX_BUFFER_STRIP_CUT_VALUE ComputeIndexBufferStripCutValue(
     wgpu::PrimitiveTopology primitiveTopology,
     wgpu::IndexFormat indexFormat) {
@@ -442,7 +422,7 @@
     descriptorD3D12.BlendState.AlphaToCoverageEnable = IsAlphaToCoverageEnabled();
     descriptorD3D12.BlendState.IndependentBlendEnable = TRUE;
 
-    descriptorD3D12.DepthStencilState = ComputeDepthStencilDesc(GetDepthStencilState());
+    descriptorD3D12.DepthStencilState = ComputeDepthStencilDesc();
 
     descriptorD3D12.SampleMask = GetSampleMask();
     descriptorD3D12.PrimitiveTopologyType = D3D12PrimitiveTopologyType(GetPrimitiveTopology());
@@ -569,4 +549,26 @@
     return inputLayoutDescriptor;
 }
 
+D3D12_DEPTH_STENCIL_DESC RenderPipeline::ComputeDepthStencilDesc() {
+    const DepthStencilState* descriptor = GetDepthStencilState();
+
+    D3D12_DEPTH_STENCIL_DESC depthStencilDescriptor = {};
+    depthStencilDescriptor.DepthEnable =
+        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
+         !descriptor->depthWriteEnabled)
+            ? FALSE
+            : TRUE;
+    depthStencilDescriptor.DepthWriteMask =
+        descriptor->depthWriteEnabled ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
+    depthStencilDescriptor.DepthFunc = ToD3D12ComparisonFunc(descriptor->depthCompare);
+
+    depthStencilDescriptor.StencilEnable = UsesStencil() ? TRUE : FALSE;
+    depthStencilDescriptor.StencilReadMask = static_cast<UINT8>(descriptor->stencilReadMask);
+    depthStencilDescriptor.StencilWriteMask = static_cast<UINT8>(descriptor->stencilWriteMask);
+
+    depthStencilDescriptor.FrontFace = StencilOpDesc(descriptor->stencilFront);
+    depthStencilDescriptor.BackFace = StencilOpDesc(descriptor->stencilBack);
+    return depthStencilDescriptor;
+}
+
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/RenderPipelineD3D12.h b/src/dawn/native/d3d12/RenderPipelineD3D12.h
index 2e4d449..f8b6cfc 100644
--- a/src/dawn/native/d3d12/RenderPipelineD3D12.h
+++ b/src/dawn/native/d3d12/RenderPipelineD3D12.h
@@ -66,6 +66,7 @@
     using RenderPipelineBase::RenderPipelineBase;
     D3D12_INPUT_LAYOUT_DESC ComputeInputLayout(
         std::array<D3D12_INPUT_ELEMENT_DESC, kMaxVertexAttributes>* inputElementDescriptors);
+    D3D12_DEPTH_STENCIL_DESC ComputeDepthStencilDesc();
 
     D3D12_PRIMITIVE_TOPOLOGY mD3d12PrimitiveTopology;
     ComPtr<ID3D12PipelineState> mPipelineState;
diff --git a/src/dawn/native/metal/RenderPipelineMTL.h b/src/dawn/native/metal/RenderPipelineMTL.h
index 3021f3b..a9d9d61 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.h
+++ b/src/dawn/native/metal/RenderPipelineMTL.h
@@ -67,6 +67,7 @@
     using RenderPipelineBase::RenderPipelineBase;
 
     NSRef<MTLVertexDescriptor> MakeVertexDesc() const;
+    NSRef<MTLDepthStencilDescriptor> MakeDepthStencilDesc();
 
     MTLPrimitiveType mMtlPrimitiveTopology;
     MTLWinding mMtlFrontFace;
diff --git a/src/dawn/native/metal/RenderPipelineMTL.mm b/src/dawn/native/metal/RenderPipelineMTL.mm
index 56a58b5..f0ddef5 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.mm
+++ b/src/dawn/native/metal/RenderPipelineMTL.mm
@@ -282,50 +282,6 @@
     DAWN_UNREACHABLE();
 }
 
-NSRef<MTLDepthStencilDescriptor> MakeDepthStencilDesc(const DepthStencilState* descriptor) {
-    NSRef<MTLDepthStencilDescriptor> mtlDepthStencilDescRef =
-        AcquireNSRef([MTLDepthStencilDescriptor new]);
-    MTLDepthStencilDescriptor* mtlDepthStencilDescriptor = mtlDepthStencilDescRef.Get();
-
-    mtlDepthStencilDescriptor.depthCompareFunction =
-        ToMetalCompareFunction(descriptor->depthCompare);
-    mtlDepthStencilDescriptor.depthWriteEnabled = descriptor->depthWriteEnabled;
-
-    if (StencilTestEnabled(descriptor)) {
-        NSRef<MTLStencilDescriptor> backFaceStencilRef = AcquireNSRef([MTLStencilDescriptor new]);
-        MTLStencilDescriptor* backFaceStencil = backFaceStencilRef.Get();
-        NSRef<MTLStencilDescriptor> frontFaceStencilRef = AcquireNSRef([MTLStencilDescriptor new]);
-        MTLStencilDescriptor* frontFaceStencil = frontFaceStencilRef.Get();
-
-        backFaceStencil.stencilCompareFunction =
-            ToMetalCompareFunction(descriptor->stencilBack.compare);
-        backFaceStencil.stencilFailureOperation =
-            MetalStencilOperation(descriptor->stencilBack.failOp);
-        backFaceStencil.depthFailureOperation =
-            MetalStencilOperation(descriptor->stencilBack.depthFailOp);
-        backFaceStencil.depthStencilPassOperation =
-            MetalStencilOperation(descriptor->stencilBack.passOp);
-        backFaceStencil.readMask = descriptor->stencilReadMask;
-        backFaceStencil.writeMask = descriptor->stencilWriteMask;
-
-        frontFaceStencil.stencilCompareFunction =
-            ToMetalCompareFunction(descriptor->stencilFront.compare);
-        frontFaceStencil.stencilFailureOperation =
-            MetalStencilOperation(descriptor->stencilFront.failOp);
-        frontFaceStencil.depthFailureOperation =
-            MetalStencilOperation(descriptor->stencilFront.depthFailOp);
-        frontFaceStencil.depthStencilPassOperation =
-            MetalStencilOperation(descriptor->stencilFront.passOp);
-        frontFaceStencil.readMask = descriptor->stencilReadMask;
-        frontFaceStencil.writeMask = descriptor->stencilWriteMask;
-
-        mtlDepthStencilDescriptor.backFaceStencil = backFaceStencil;
-        mtlDepthStencilDescriptor.frontFaceStencil = frontFaceStencil;
-    }
-
-    return mtlDepthStencilDescRef;
-}
-
 MTLWinding MTLFrontFace(wgpu::FrontFace face) {
     switch (face) {
         case wgpu::FrontFace::CW:
@@ -494,8 +450,7 @@
     // Create depth stencil state and cache it, fetch the cached depth stencil state when we
     // call setDepthStencilState() for a given render pipeline in CommandEncoder, in order
     // to improve performance.
-    NSRef<MTLDepthStencilDescriptor> depthStencilDesc =
-        MakeDepthStencilDesc(GetDepthStencilState());
+    NSRef<MTLDepthStencilDescriptor> depthStencilDesc = MakeDepthStencilDesc();
     mMtlDepthStencilState =
         AcquireNSPRef([mtlDevice newDepthStencilStateWithDescriptor:depthStencilDesc.Get()]);
 
@@ -582,4 +537,50 @@
     return AcquireNSRef(mtlVertexDescriptor);
 }
 
+NSRef<MTLDepthStencilDescriptor> RenderPipeline::MakeDepthStencilDesc() {
+    const DepthStencilState* descriptor = GetDepthStencilState();
+
+    NSRef<MTLDepthStencilDescriptor> mtlDepthStencilDescRef =
+        AcquireNSRef([MTLDepthStencilDescriptor new]);
+    MTLDepthStencilDescriptor* mtlDepthStencilDescriptor = mtlDepthStencilDescRef.Get();
+
+    mtlDepthStencilDescriptor.depthCompareFunction =
+        ToMetalCompareFunction(descriptor->depthCompare);
+    mtlDepthStencilDescriptor.depthWriteEnabled = descriptor->depthWriteEnabled;
+
+    if (UsesStencil()) {
+        NSRef<MTLStencilDescriptor> backFaceStencilRef = AcquireNSRef([MTLStencilDescriptor new]);
+        MTLStencilDescriptor* backFaceStencil = backFaceStencilRef.Get();
+        NSRef<MTLStencilDescriptor> frontFaceStencilRef = AcquireNSRef([MTLStencilDescriptor new]);
+        MTLStencilDescriptor* frontFaceStencil = frontFaceStencilRef.Get();
+
+        backFaceStencil.stencilCompareFunction =
+            ToMetalCompareFunction(descriptor->stencilBack.compare);
+        backFaceStencil.stencilFailureOperation =
+            MetalStencilOperation(descriptor->stencilBack.failOp);
+        backFaceStencil.depthFailureOperation =
+            MetalStencilOperation(descriptor->stencilBack.depthFailOp);
+        backFaceStencil.depthStencilPassOperation =
+            MetalStencilOperation(descriptor->stencilBack.passOp);
+        backFaceStencil.readMask = descriptor->stencilReadMask;
+        backFaceStencil.writeMask = descriptor->stencilWriteMask;
+
+        frontFaceStencil.stencilCompareFunction =
+            ToMetalCompareFunction(descriptor->stencilFront.compare);
+        frontFaceStencil.stencilFailureOperation =
+            MetalStencilOperation(descriptor->stencilFront.failOp);
+        frontFaceStencil.depthFailureOperation =
+            MetalStencilOperation(descriptor->stencilFront.depthFailOp);
+        frontFaceStencil.depthStencilPassOperation =
+            MetalStencilOperation(descriptor->stencilFront.passOp);
+        frontFaceStencil.readMask = descriptor->stencilReadMask;
+        frontFaceStencil.writeMask = descriptor->stencilWriteMask;
+
+        mtlDepthStencilDescriptor.backFaceStencil = backFaceStencil;
+        mtlDepthStencilDescriptor.frontFaceStencil = frontFaceStencil;
+    }
+
+    return mtlDepthStencilDescRef;
+}
+
 }  // namespace dawn::native::metal
diff --git a/src/dawn/native/opengl/RenderPipelineGL.cpp b/src/dawn/native/opengl/RenderPipelineGL.cpp
index 86103f0..a4c2b08 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn/native/opengl/RenderPipelineGL.cpp
@@ -200,46 +200,6 @@
     DAWN_UNREACHABLE();
 }
 
-void ApplyDepthStencilState(const OpenGLFunctions& gl,
-                            const DepthStencilState* descriptor,
-                            PersistentPipelineState* persistentPipelineState) {
-    // Depth writes only occur if depth is enabled
-    if (descriptor->depthCompare == wgpu::CompareFunction::Always &&
-        !descriptor->depthWriteEnabled) {
-        gl.Disable(GL_DEPTH_TEST);
-    } else {
-        gl.Enable(GL_DEPTH_TEST);
-    }
-
-    if (descriptor->depthWriteEnabled) {
-        gl.DepthMask(GL_TRUE);
-    } else {
-        gl.DepthMask(GL_FALSE);
-    }
-
-    gl.DepthFunc(ToOpenGLCompareFunction(descriptor->depthCompare));
-
-    if (StencilTestEnabled(descriptor)) {
-        gl.Enable(GL_STENCIL_TEST);
-    } else {
-        gl.Disable(GL_STENCIL_TEST);
-    }
-
-    GLenum backCompareFunction = ToOpenGLCompareFunction(descriptor->stencilBack.compare);
-    GLenum frontCompareFunction = ToOpenGLCompareFunction(descriptor->stencilFront.compare);
-    persistentPipelineState->SetStencilFuncsAndMask(gl, backCompareFunction, frontCompareFunction,
-                                                    descriptor->stencilReadMask);
-
-    gl.StencilOpSeparate(GL_BACK, OpenGLStencilOperation(descriptor->stencilBack.failOp),
-                         OpenGLStencilOperation(descriptor->stencilBack.depthFailOp),
-                         OpenGLStencilOperation(descriptor->stencilBack.passOp));
-    gl.StencilOpSeparate(GL_FRONT, OpenGLStencilOperation(descriptor->stencilFront.failOp),
-                         OpenGLStencilOperation(descriptor->stencilFront.depthFailOp),
-                         OpenGLStencilOperation(descriptor->stencilFront.passOp));
-
-    gl.StencilMask(descriptor->stencilWriteMask);
-}
-
 }  // anonymous namespace
 
 // static
@@ -323,7 +283,7 @@
 
     ApplyFrontFaceAndCulling(gl, GetFrontFace(), GetCullMode());
 
-    ApplyDepthStencilState(gl, GetDepthStencilState(), &persistentPipelineState);
+    ApplyDepthStencilState(gl, &persistentPipelineState);
 
     gl.SampleMaski(0, GetSampleMask());
     if (IsAlphaToCoverageEnabled()) {
@@ -379,4 +339,45 @@
     }
 }
 
+void RenderPipeline::ApplyDepthStencilState(const OpenGLFunctions& gl,
+                                            PersistentPipelineState* persistentPipelineState) {
+    const DepthStencilState* descriptor = GetDepthStencilState();
+
+    // Depth writes only occur if depth is enabled
+    if (descriptor->depthCompare == wgpu::CompareFunction::Always &&
+        !descriptor->depthWriteEnabled) {
+        gl.Disable(GL_DEPTH_TEST);
+    } else {
+        gl.Enable(GL_DEPTH_TEST);
+    }
+
+    if (descriptor->depthWriteEnabled) {
+        gl.DepthMask(GL_TRUE);
+    } else {
+        gl.DepthMask(GL_FALSE);
+    }
+
+    gl.DepthFunc(ToOpenGLCompareFunction(descriptor->depthCompare));
+
+    if (UsesStencil()) {
+        gl.Enable(GL_STENCIL_TEST);
+    } else {
+        gl.Disable(GL_STENCIL_TEST);
+    }
+
+    GLenum backCompareFunction = ToOpenGLCompareFunction(descriptor->stencilBack.compare);
+    GLenum frontCompareFunction = ToOpenGLCompareFunction(descriptor->stencilFront.compare);
+    persistentPipelineState->SetStencilFuncsAndMask(gl, backCompareFunction, frontCompareFunction,
+                                                    descriptor->stencilReadMask);
+
+    gl.StencilOpSeparate(GL_BACK, OpenGLStencilOperation(descriptor->stencilBack.failOp),
+                         OpenGLStencilOperation(descriptor->stencilBack.depthFailOp),
+                         OpenGLStencilOperation(descriptor->stencilBack.passOp));
+    gl.StencilOpSeparate(GL_FRONT, OpenGLStencilOperation(descriptor->stencilFront.failOp),
+                         OpenGLStencilOperation(descriptor->stencilFront.depthFailOp),
+                         OpenGLStencilOperation(descriptor->stencilFront.passOp));
+
+    gl.StencilMask(descriptor->stencilWriteMask);
+}
+
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/RenderPipelineGL.h b/src/dawn/native/opengl/RenderPipelineGL.h
index 499c3ab..80f411d 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.h
+++ b/src/dawn/native/opengl/RenderPipelineGL.h
@@ -60,6 +60,9 @@
 
     void CreateVAOForVertexState();
 
+    void ApplyDepthStencilState(const OpenGLFunctions& gl,
+                                PersistentPipelineState* persistentPipelineState);
+
     // TODO(yunchao.he@intel.com): vao need to be deduplicated between pipelines.
     GLuint mVertexArrayObject;
     GLenum mGlPrimitiveTopology;
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 1a78fed..b8a7441 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -320,49 +320,6 @@
     DAWN_UNREACHABLE();
 }
 
-VkPipelineDepthStencilStateCreateInfo ComputeDepthStencilDesc(const DepthStencilState* descriptor) {
-    VkPipelineDepthStencilStateCreateInfo depthStencilState;
-    depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
-    depthStencilState.pNext = nullptr;
-    depthStencilState.flags = 0;
-
-    // Depth writes only occur if depth is enabled
-    depthStencilState.depthTestEnable =
-        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
-         !descriptor->depthWriteEnabled)
-            ? VK_FALSE
-            : VK_TRUE;
-    depthStencilState.depthWriteEnable = descriptor->depthWriteEnabled ? VK_TRUE : VK_FALSE;
-    depthStencilState.depthCompareOp = ToVulkanCompareOp(descriptor->depthCompare);
-    depthStencilState.depthBoundsTestEnable = false;
-    depthStencilState.minDepthBounds = 0.0f;
-    depthStencilState.maxDepthBounds = 1.0f;
-
-    depthStencilState.stencilTestEnable = StencilTestEnabled(descriptor) ? VK_TRUE : VK_FALSE;
-
-    depthStencilState.front.failOp = VulkanStencilOp(descriptor->stencilFront.failOp);
-    depthStencilState.front.passOp = VulkanStencilOp(descriptor->stencilFront.passOp);
-    depthStencilState.front.depthFailOp = VulkanStencilOp(descriptor->stencilFront.depthFailOp);
-    depthStencilState.front.compareOp = ToVulkanCompareOp(descriptor->stencilFront.compare);
-
-    depthStencilState.back.failOp = VulkanStencilOp(descriptor->stencilBack.failOp);
-    depthStencilState.back.passOp = VulkanStencilOp(descriptor->stencilBack.passOp);
-    depthStencilState.back.depthFailOp = VulkanStencilOp(descriptor->stencilBack.depthFailOp);
-    depthStencilState.back.compareOp = ToVulkanCompareOp(descriptor->stencilBack.compare);
-
-    // Dawn doesn't have separate front and back stencil masks.
-    depthStencilState.front.compareMask = descriptor->stencilReadMask;
-    depthStencilState.back.compareMask = descriptor->stencilReadMask;
-    depthStencilState.front.writeMask = descriptor->stencilWriteMask;
-    depthStencilState.back.writeMask = descriptor->stencilWriteMask;
-
-    // The stencil reference is always dynamic
-    depthStencilState.front.reference = 0;
-    depthStencilState.back.reference = 0;
-
-    return depthStencilState;
-}
-
 }  // anonymous namespace
 
 // static
@@ -486,8 +443,7 @@
     multisample.alphaToCoverageEnable = IsAlphaToCoverageEnabled();
     multisample.alphaToOneEnable = VK_FALSE;
 
-    VkPipelineDepthStencilStateCreateInfo depthStencilState =
-        ComputeDepthStencilDesc(GetDepthStencilState());
+    VkPipelineDepthStencilStateCreateInfo depthStencilState = ComputeDepthStencilDesc();
 
     VkPipelineColorBlendStateCreateInfo colorBlend;
     // colorBlend may hold pointers to elements in colorBlendAttachments, so it must have a
@@ -667,6 +623,51 @@
     return createInfo;
 }
 
+VkPipelineDepthStencilStateCreateInfo RenderPipeline::ComputeDepthStencilDesc() {
+    const DepthStencilState* descriptor = GetDepthStencilState();
+
+    VkPipelineDepthStencilStateCreateInfo depthStencilState;
+    depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+    depthStencilState.pNext = nullptr;
+    depthStencilState.flags = 0;
+
+    // Depth writes only occur if depth is enabled
+    depthStencilState.depthTestEnable =
+        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
+         !descriptor->depthWriteEnabled)
+            ? VK_FALSE
+            : VK_TRUE;
+    depthStencilState.depthWriteEnable = descriptor->depthWriteEnabled ? VK_TRUE : VK_FALSE;
+    depthStencilState.depthCompareOp = ToVulkanCompareOp(descriptor->depthCompare);
+    depthStencilState.depthBoundsTestEnable = false;
+    depthStencilState.minDepthBounds = 0.0f;
+    depthStencilState.maxDepthBounds = 1.0f;
+
+    depthStencilState.stencilTestEnable = UsesStencil() ? VK_TRUE : VK_FALSE;
+
+    depthStencilState.front.failOp = VulkanStencilOp(descriptor->stencilFront.failOp);
+    depthStencilState.front.passOp = VulkanStencilOp(descriptor->stencilFront.passOp);
+    depthStencilState.front.depthFailOp = VulkanStencilOp(descriptor->stencilFront.depthFailOp);
+    depthStencilState.front.compareOp = ToVulkanCompareOp(descriptor->stencilFront.compare);
+
+    depthStencilState.back.failOp = VulkanStencilOp(descriptor->stencilBack.failOp);
+    depthStencilState.back.passOp = VulkanStencilOp(descriptor->stencilBack.passOp);
+    depthStencilState.back.depthFailOp = VulkanStencilOp(descriptor->stencilBack.depthFailOp);
+    depthStencilState.back.compareOp = ToVulkanCompareOp(descriptor->stencilBack.compare);
+
+    // Dawn doesn't have separate front and back stencil masks.
+    depthStencilState.front.compareMask = descriptor->stencilReadMask;
+    depthStencilState.back.compareMask = descriptor->stencilReadMask;
+    depthStencilState.front.writeMask = descriptor->stencilWriteMask;
+    depthStencilState.back.writeMask = descriptor->stencilWriteMask;
+
+    // The stencil reference is always dynamic
+    depthStencilState.front.reference = 0;
+    depthStencilState.back.reference = 0;
+
+    return depthStencilState;
+}
+
 RenderPipeline::~RenderPipeline() = default;
 
 void RenderPipeline::DestroyImpl() {
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.h b/src/dawn/native/vulkan/RenderPipelineVk.h
index 077a3cc..003aec3 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.h
+++ b/src/dawn/native/vulkan/RenderPipelineVk.h
@@ -61,6 +61,7 @@
     };
     VkPipelineVertexInputStateCreateInfo ComputeVertexInputDesc(
         PipelineVertexInputStateCreateInfoTemporaryAllocations* temporaryAllocations);
+    VkPipelineDepthStencilStateCreateInfo ComputeDepthStencilDesc();
 
     VkPipeline mHandle = VK_NULL_HANDLE;
 };
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index a43dbe4..e4292ac 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -155,6 +155,42 @@
         device.CreateRenderPipeline(&descriptor);
     }
 
+    // Control case, stencil faces with Keep/Always are valid when the format doesn't have stencil
+    {
+        utils::ComboRenderPipelineDescriptor descriptor;
+        descriptor.vertex.module = vsModule;
+        descriptor.cFragment.module = fsModule;
+        wgpu::DepthStencilState* depthStencil =
+            descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
+        depthStencil->stencilFront.compare = wgpu::CompareFunction::Always;
+        depthStencil->stencilFront.failOp = wgpu::StencilOperation::Keep;
+        depthStencil->stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
+        depthStencil->stencilFront.passOp = wgpu::StencilOperation::Keep;
+        depthStencil->stencilBack.compare = wgpu::CompareFunction::Always;
+        depthStencil->stencilBack.failOp = wgpu::StencilOperation::Keep;
+        depthStencil->stencilBack.depthFailOp = wgpu::StencilOperation::Keep;
+        depthStencil->stencilBack.passOp = wgpu::StencilOperation::Keep;
+        device.CreateRenderPipeline(&descriptor);
+    }
+
+    // Control case, stencil faces with Undefined are valid when the format doesn't have stencil
+    {
+        utils::ComboRenderPipelineDescriptor descriptor;
+        descriptor.vertex.module = vsModule;
+        descriptor.cFragment.module = fsModule;
+        wgpu::DepthStencilState* depthStencil =
+            descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
+        depthStencil->stencilFront.compare = wgpu::CompareFunction::Undefined;
+        depthStencil->stencilFront.failOp = wgpu::StencilOperation::Undefined;
+        depthStencil->stencilFront.depthFailOp = wgpu::StencilOperation::Undefined;
+        depthStencil->stencilFront.passOp = wgpu::StencilOperation::Undefined;
+        depthStencil->stencilBack.compare = wgpu::CompareFunction::Undefined;
+        depthStencil->stencilBack.failOp = wgpu::StencilOperation::Undefined;
+        depthStencil->stencilBack.depthFailOp = wgpu::StencilOperation::Undefined;
+        depthStencil->stencilBack.passOp = wgpu::StencilOperation::Undefined;
+        device.CreateRenderPipeline(&descriptor);
+    }
+
     // It is invalid if the texture format doesn't have stencil aspect while stencil test is
     // enabled (depthStencilState.stencilFront are not default values).
     {