Simplify PersistentPipelineState and application of stencil states. Fix
stencil mask usage. D3D12 does not support separate front/back masks.
All APIs support separate read/write masks.
diff --git a/examples/HelloDepthStencil.cpp b/examples/HelloDepthStencil.cpp
index a23fc35..05afce5 100644
--- a/examples/HelloDepthStencil.cpp
+++ b/examples/HelloDepthStencil.cpp
@@ -102,7 +102,7 @@
     planeBuffer = CreateFrozenBufferFromData(device, (void*)planeData, sizeof(planeData), nxt::BufferUsageBit::Vertex);
 }
 
-struct {
+struct CameraData {
     glm::mat4 view;
     glm::mat4 proj;
 } cameraData;
@@ -167,31 +167,17 @@
     cameraBuffer = device.CreateBufferBuilder()
         .SetAllowedUsage(nxt::BufferUsageBit::Mapped | nxt::BufferUsageBit::Uniform)
         .SetInitialUsage(nxt::BufferUsageBit::Mapped)
-        .SetSize(sizeof(cameraData))
+        .SetSize(sizeof(CameraData))
         .GetResult();
 
     glm::mat4 transform(1.0);
-
-    transformBuffer[0] = device.CreateBufferBuilder()
-        .SetAllowedUsage(nxt::BufferUsageBit::Mapped | nxt::BufferUsageBit::Uniform)
-        .SetInitialUsage(nxt::BufferUsageBit::Mapped)
-        .SetSize(sizeof(glm::mat4))
-        .GetResult();
-    transformBuffer[0].SetSubData(0, sizeof(glm::mat4) / sizeof(uint32_t), reinterpret_cast<uint32_t*>(&transform));
-    transformBuffer[0].FreezeUsage(nxt::BufferUsageBit::Uniform);
+    transformBuffer[0] = CreateFrozenBufferFromData(device, (void*)&transform, sizeof(glm::mat4), nxt::BufferUsageBit::Uniform);
 
     transform = glm::translate(transform, glm::vec3(0.f, -2.f, 0.f));
-
-    transformBuffer[1] = device.CreateBufferBuilder()
-        .SetAllowedUsage(nxt::BufferUsageBit::Mapped | nxt::BufferUsageBit::Uniform)
-        .SetInitialUsage(nxt::BufferUsageBit::Mapped)
-        .SetSize(sizeof(glm::mat4))
-        .GetResult();
-    transformBuffer[1].SetSubData(0, sizeof(glm::mat4) / sizeof(uint32_t), reinterpret_cast<uint32_t*>(&transform));
-    transformBuffer[1].FreezeUsage(nxt::BufferUsageBit::Uniform);
+    transformBuffer[1] = CreateFrozenBufferFromData(device, (void*)&transform, sizeof(glm::mat4), nxt::BufferUsageBit::Uniform);
 
     nxt::BufferView cameraBufferView = cameraBuffer.CreateBufferViewBuilder()
-        .SetExtent(0, sizeof(cameraData))
+        .SetExtent(0, sizeof(CameraData))
         .GetResult();
 
     nxt::BufferView transformBufferView[2] = {
@@ -280,7 +266,7 @@
     );
 
     cameraBuffer.TransitionUsage(nxt::BufferUsageBit::Mapped);
-    cameraBuffer.SetSubData(0, sizeof(cameraData) / sizeof(uint32_t), reinterpret_cast<uint32_t*>(&cameraData));
+    cameraBuffer.SetSubData(0, sizeof(CameraData) / sizeof(uint32_t), reinterpret_cast<uint32_t*>(&cameraData));
 
     nxt::CommandBuffer commands = device.CreateCommandBufferBuilder()
         .BeginRenderPass(renderpass, framebuffer)
diff --git a/next.json b/next.json
index 49dc464..c74d95f 100644
--- a/next.json
+++ b/next.json
@@ -475,8 +475,8 @@
             {
                 "name": "set stencil mask",
                 "args": [
-                    {"name": "face", "type": "face"},
-                    {"name": "mask", "type": "uint32_t"}
+                    {"name": "readMask", "type": "uint32_t"},
+                    {"name": "writeMask", "type": "uint32_t"}
                 ]
             }
         ]
diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt
index d25d64c..88a70ab 100644
--- a/src/backend/CMakeLists.txt
+++ b/src/backend/CMakeLists.txt
@@ -83,6 +83,8 @@
 list(APPEND BACKEND_SOURCES
     ${OPENGL_DIR}/CommandBufferGL.cpp
     ${OPENGL_DIR}/CommandBufferGL.h
+    ${OPENGL_DIR}/DepthStencilStateGL.cpp
+    ${OPENGL_DIR}/DepthStencilStateGL.h
     ${OPENGL_DIR}/OpenGLBackend.cpp
     ${OPENGL_DIR}/OpenGLBackend.h
     ${OPENGL_DIR}/PersistentPipelineStateGL.cpp
diff --git a/src/backend/common/CommandBuffer.cpp b/src/backend/common/CommandBuffer.cpp
index 21afa50..76fb13c 100644
--- a/src/backend/common/CommandBuffer.cpp
+++ b/src/backend/common/CommandBuffer.cpp
@@ -573,10 +573,6 @@
                 case Command::SetStencilReference:
                     {
                         SetStencilReferenceCmd* cmd = iterator.NextCommand<SetStencilReferenceCmd>();
-                        if (lastPipeline->IsCompute()) {
-                            HandleError("Can't set stencil reference in a compute pipeline");
-                            return false;
-                        }
                         if (currentRenderPass == nullptr) {
                             HandleError("Can't set stencil reference without an active render pass");
                             return false;
diff --git a/src/backend/common/DepthStencilState.cpp b/src/backend/common/DepthStencilState.cpp
index 3474afc..aad61e0 100644
--- a/src/backend/common/DepthStencilState.cpp
+++ b/src/backend/common/DepthStencilState.cpp
@@ -29,14 +29,16 @@
     }
 
     bool DepthStencilStateBase::StencilTestEnabled() const {
-        return (stencilInfo.back.compareFunction != nxt::CompareFunction::Always ||
-        stencilInfo.back.stencilFail != nxt::StencilOperation::Keep ||
-        stencilInfo.back.depthFail != nxt::StencilOperation::Keep ||
-        stencilInfo.back.depthStencilPass != nxt::StencilOperation::Keep ||
-        stencilInfo.front.compareFunction != nxt::CompareFunction::Always ||
-        stencilInfo.front.stencilFail != nxt::StencilOperation::Keep ||
-        stencilInfo.front.depthFail != nxt::StencilOperation::Keep ||
-        stencilInfo.front.depthStencilPass != nxt::StencilOperation::Keep);
+        return (
+            stencilInfo.back.compareFunction != nxt::CompareFunction::Always ||
+            stencilInfo.back.stencilFail != nxt::StencilOperation::Keep ||
+            stencilInfo.back.depthFail != nxt::StencilOperation::Keep ||
+            stencilInfo.back.depthStencilPass != nxt::StencilOperation::Keep ||
+            stencilInfo.front.compareFunction != nxt::CompareFunction::Always ||
+            stencilInfo.front.stencilFail != nxt::StencilOperation::Keep ||
+            stencilInfo.front.depthFail != nxt::StencilOperation::Keep ||
+            stencilInfo.front.depthStencilPass != nxt::StencilOperation::Keep
+        );
     }
 
     const DepthStencilStateBase::DepthInfo& DepthStencilStateBase::GetDepth() const {
@@ -53,9 +55,8 @@
         DEPTH_STENCIL_STATE_PROPERTY_DEPTH_COMPARE_FUNCTION = 0x1,
         DEPTH_STENCIL_STATE_PROPERTY_DEPTH_WRITE_ENABLED = 0x2,
         DEPTH_STENCIL_STATE_PROPERTY_STENCIL_BACK_FUNCTION = 0x4,
-        DEPTH_STENCIL_STATE_PROPERTY_STENCIL_BACK_MASK = 0x8,
-        DEPTH_STENCIL_STATE_PROPERTY_STENCIL_FRONT_FUNCTION = 0x10,
-        DEPTH_STENCIL_STATE_PROPERTY_STENCIL_FRONT_MASK = 0x20,
+        DEPTH_STENCIL_STATE_PROPERTY_STENCIL_FRONT_FUNCTION = 0x08,
+        DEPTH_STENCIL_STATE_PROPERTY_STENCIL_MASK = 0x10,
     };
 
     DepthStencilStateBuilder::DepthStencilStateBuilder(DeviceBase* device) : Builder(device) {
@@ -122,32 +123,15 @@
         }
     }
 
-    void DepthStencilStateBuilder::SetStencilMask(nxt::Face face, uint32_t mask) {
-        if (face == nxt::Face::None) {
-            HandleError("Can't set stencil mask of None face");
+    void DepthStencilStateBuilder::SetStencilMask(uint32_t readMask, uint32_t writeMask) {
+         if ((propertiesSet & DEPTH_STENCIL_STATE_PROPERTY_STENCIL_MASK) != 0) {
+            HandleError("Stencilmask property set multiple times");
             return;
         }
 
-        if (face & nxt::Face::Back) {
-            if ((propertiesSet & DEPTH_STENCIL_STATE_PROPERTY_STENCIL_BACK_MASK) != 0) {
-                HandleError("Stencil back mask property set multiple times");
-                return;
-            }
-
-            propertiesSet |= DEPTH_STENCIL_STATE_PROPERTY_STENCIL_BACK_MASK;
-
-            stencilInfo.back.mask = mask;
-        }
-        if (face & nxt::Face::Front) {
-            if ((propertiesSet & DEPTH_STENCIL_STATE_PROPERTY_STENCIL_FRONT_MASK) != 0) {
-                HandleError("Stencil front mask property set multiple times");
-                return;
-            }
-
-            propertiesSet |= DEPTH_STENCIL_STATE_PROPERTY_STENCIL_FRONT_MASK;
-
-            stencilInfo.front.mask = mask;
-        }
+        propertiesSet |= DEPTH_STENCIL_STATE_PROPERTY_STENCIL_MASK;
+        stencilInfo.readMask = readMask;
+        stencilInfo.writeMask = writeMask;
     }
 
 }
diff --git a/src/backend/common/DepthStencilState.h b/src/backend/common/DepthStencilState.h
index 4dee5d5..5bcaf18 100644
--- a/src/backend/common/DepthStencilState.h
+++ b/src/backend/common/DepthStencilState.h
@@ -38,13 +38,13 @@
                 nxt::StencilOperation stencilFail = nxt::StencilOperation::Keep;
                 nxt::StencilOperation depthFail = nxt::StencilOperation::Keep;
                 nxt::StencilOperation depthStencilPass = nxt::StencilOperation::Keep;
-                uint32_t mask = 0xff;
             };
 
             struct StencilInfo {
-                bool stencilTestEnabled;
                 StencilFaceInfo back;
                 StencilFaceInfo front;
+                uint32_t readMask = 0xff;
+                uint32_t writeMask = 0xff;
             };
 
             bool DepthTestEnabled() const;
@@ -66,7 +66,7 @@
             void SetDepthWriteEnabled(bool enabled);
             void SetStencilFunction(nxt::Face face, nxt::CompareFunction stencilCompareFunction,
                 nxt::StencilOperation stencilFail, nxt::StencilOperation depthFail, nxt::StencilOperation depthStencilPass);
-            void SetStencilMask(nxt::Face face, uint32_t mask);
+            void SetStencilMask(uint32_t readMask, uint32_t writeMask);
 
         private:
             friend class DepthStencilStateBase;
diff --git a/src/backend/common/Pipeline.cpp b/src/backend/common/Pipeline.cpp
index 3902a8d..2ea4adb 100644
--- a/src/backend/common/Pipeline.cpp
+++ b/src/backend/common/Pipeline.cpp
@@ -125,7 +125,7 @@
         if (!layout) {
             layout = device->CreatePipelineLayoutBuilder()->GetResult();
         }
-        if (!inputState) {
+        if (!inputState && !IsCompute()) {
             inputState = device->CreateInputStateBuilder()->GetResult();
         }
         if (!depthStencilState && !IsCompute()) {
diff --git a/src/backend/metal/MetalBackend.mm b/src/backend/metal/MetalBackend.mm
index 44ebaabc..12c6681 100644
--- a/src/backend/metal/MetalBackend.mm
+++ b/src/backend/metal/MetalBackend.mm
@@ -727,15 +727,15 @@
             backFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.back.stencilFail);
             backFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.back.depthFail);
             backFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.back.depthStencilPass);
-            backFaceStencil.readMask = stencil.back.mask;
-            backFaceStencil.writeMask = stencil.back.mask;
+            backFaceStencil.readMask = stencil.readMask;
+            backFaceStencil.writeMask = stencil.writeMask;
 
             frontFaceStencil.stencilCompareFunction = MetalDepthStencilCompareFunction(stencil.front.compareFunction);
             frontFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.front.stencilFail);
             frontFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.front.depthFail);
             frontFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.front.depthStencilPass);
-            frontFaceStencil.readMask = stencil.front.mask;
-            frontFaceStencil.writeMask = stencil.front.mask;
+            frontFaceStencil.readMask = stencil.readMask;
+            frontFaceStencil.writeMask = stencil.writeMask;
 
             mtlDepthStencilDescriptor.backFaceStencil = backFaceStencil;
             mtlDepthStencilDescriptor.frontFaceStencil = frontFaceStencil;
diff --git a/src/backend/opengl/CommandBufferGL.cpp b/src/backend/opengl/CommandBufferGL.cpp
index 0654bd9..501db18 100644
--- a/src/backend/opengl/CommandBufferGL.cpp
+++ b/src/backend/opengl/CommandBufferGL.cpp
@@ -27,8 +27,6 @@
 namespace backend {
 namespace opengl {
 
-    PersistentPipelineState persistentPipelineState;
-
     CommandBuffer::CommandBuffer(Device* device, CommandBufferBuilder* builder)
         : CommandBufferBase(builder), device(device), commands(builder->AcquireCommands()) {
     }
@@ -61,6 +59,9 @@
         uint32_t indexBufferOffset = 0;
         nxt::IndexFormat indexBufferFormat = nxt::IndexFormat::Uint16;
 
+        PersistentPipelineState persistentPipelineState;
+        persistentPipelineState.SetDefaultState();
+
         while(commands.NextCommandId(&type)) {
             switch (type) {
                 case Command::AdvanceSubpass:
@@ -184,9 +185,8 @@
                 case Command::SetStencilReference:
                     {
                         SetStencilReferenceCmd* cmd = commands.NextCommand<SetStencilReferenceCmd>();
-                        DepthStencilState* depthStencilState = ToBackend(lastPipeline->GetDepthStencilState());
-                        persistentPipelineState.UpdateStencilReference(cmd->reference);
-                        persistentPipelineState.ApplyStencilNow();
+                        // DepthStencilState* depthStencilState = ToBackend(lastPipeline->GetDepthStencilState());
+                        persistentPipelineState.SetStencilReference(cmd->reference);
                     }
                     break;
 
diff --git a/src/backend/opengl/DepthStencilStateGL.cpp b/src/backend/opengl/DepthStencilStateGL.cpp
new file mode 100644
index 0000000..97c95b0
--- /dev/null
+++ b/src/backend/opengl/DepthStencilStateGL.cpp
@@ -0,0 +1,128 @@
+// Copyright 2017 The NXT 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 "DepthStencilStateGL.h"
+
+#include "OpenGLBackend.h"
+#include "PersistentPipelineStateGL.h"
+
+namespace backend {
+namespace opengl {
+
+    namespace {
+        GLuint OpenGLCompareFunction(nxt::CompareFunction compareFunction) {
+            switch (compareFunction) {
+                case nxt::CompareFunction::Never:
+                    return GL_NEVER;
+                case nxt::CompareFunction::Less:
+                    return GL_LESS;
+                case nxt::CompareFunction::LessEqual:
+                    return GL_LEQUAL;
+                case nxt::CompareFunction::Greater:
+                    return GL_GREATER;
+                case nxt::CompareFunction::GreaterEqual:
+                    return GL_GEQUAL;
+                case nxt::CompareFunction::NotEqual:
+                    return GL_NOTEQUAL;
+                case nxt::CompareFunction::Equal:
+                    return GL_EQUAL;
+                case nxt::CompareFunction::Always:
+                    return GL_ALWAYS;
+            }
+        }
+
+        GLuint OpenGLStencilOperation(nxt::StencilOperation stencilOperation) {
+            switch (stencilOperation) {
+                case nxt::StencilOperation::Keep:
+                    return GL_KEEP;
+                case nxt::StencilOperation::Zero:
+                    return GL_ZERO;
+                case nxt::StencilOperation::Replace:
+                    return GL_REPLACE;
+                case nxt::StencilOperation::Invert:
+                    return GL_INVERT;
+                case nxt::StencilOperation::IncrementClamp:
+                    return GL_INCR;
+                case nxt::StencilOperation::DecrementClamp:
+                    return GL_DECR;
+                case nxt::StencilOperation::IncrementWrap:
+                    return GL_INCR_WRAP;
+                case nxt::StencilOperation::DecrementWrap:
+                    return GL_DECR_WRAP;
+            }
+        }
+    }
+
+    DepthStencilState::DepthStencilState(Device* device, DepthStencilStateBuilder* builder)
+        : DepthStencilStateBase(builder), device(device) {
+    }
+
+    void DepthStencilState::ApplyNow(PersistentPipelineState &persistentPipelineState) const {
+        if (DepthTestEnabled()) {
+            glEnable(GL_DEPTH_TEST);
+        } else {
+            glDisable(GL_DEPTH_TEST);
+        }
+
+        auto& depthInfo = GetDepth();
+
+        if (depthInfo.depthWriteEnabled) {
+            glDepthMask(GL_TRUE);
+        } else {
+            glDepthMask(GL_FALSE);
+        }
+
+        glDepthFunc(OpenGLCompareFunction(depthInfo.compareFunction));
+
+        if (StencilTestEnabled()) {
+            glEnable(GL_STENCIL_TEST);
+        } else {
+            glDisable(GL_STENCIL_TEST);
+        }
+
+        auto& stencilInfo = GetStencil();
+
+        GLenum backCompareFunction = OpenGLCompareFunction(stencilInfo.back.compareFunction);
+        GLenum frontCompareFunction = OpenGLCompareFunction(stencilInfo.front.compareFunction);
+
+        persistentPipelineState.CacheStencilFuncsAndMask(backCompareFunction, frontCompareFunction, stencilInfo.readMask);
+
+        glStencilOpSeparate(GL_BACK,
+            OpenGLStencilOperation(stencilInfo.back.stencilFail),
+            OpenGLStencilOperation(stencilInfo.back.depthFail),
+            OpenGLStencilOperation(stencilInfo.back.depthStencilPass)
+        );
+        glStencilFuncSeparate(GL_BACK,
+            backCompareFunction,
+            persistentPipelineState.GetCachedStencilReference(),
+            stencilInfo.readMask
+        );
+        glStencilMaskSeparate(GL_BACK, stencilInfo.writeMask);
+
+        glStencilOpSeparate(GL_FRONT,
+            OpenGLStencilOperation(stencilInfo.front.stencilFail),
+            OpenGLStencilOperation(stencilInfo.front.depthFail),
+            OpenGLStencilOperation(stencilInfo.front.depthStencilPass)
+        );
+        glStencilFuncSeparate(GL_FRONT,
+            frontCompareFunction,
+            persistentPipelineState.GetCachedStencilReference(),
+            stencilInfo.readMask
+        );
+        glStencilMaskSeparate(GL_FRONT, stencilInfo.writeMask);
+
+    }
+
+}
+}
diff --git a/src/backend/opengl/DepthStencilStateGL.h b/src/backend/opengl/DepthStencilStateGL.h
new file mode 100644
index 0000000..9eba5ed
--- /dev/null
+++ b/src/backend/opengl/DepthStencilStateGL.h
@@ -0,0 +1,39 @@
+// Copyright 2017 The NXT 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.
+
+#ifndef BACKEND_OPENGL_DEPTHSTENCILSTATEGL_H_
+#define BACKEND_OPENGL_DEPTHSTENCILSTATEGL_H_
+
+#include "common/DepthStencilState.h"
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+    class PersistentPipelineState;
+
+    class DepthStencilState : public DepthStencilStateBase {
+        public:
+            DepthStencilState(Device* device, DepthStencilStateBuilder* builder);
+
+            void ApplyNow(PersistentPipelineState &persistentPipelineState) const;
+
+        private:
+            Device* device;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_DEPTHSTENCILSTATEGL_H_
diff --git a/src/backend/opengl/GeneratedCodeIncludes.h b/src/backend/opengl/GeneratedCodeIncludes.h
index b37ebcd..bc9913f 100644
--- a/src/backend/opengl/GeneratedCodeIncludes.h
+++ b/src/backend/opengl/GeneratedCodeIncludes.h
@@ -14,6 +14,7 @@
 
 #include "OpenGLBackend.h"
 #include "CommandBufferGL.h"
+#include "DepthStencilStateGL.h"
 #include "PersistentPipelineStateGL.h"
 #include "PipelineGL.h"
 #include "PipelineLayoutGL.h"
diff --git a/src/backend/opengl/OpenGLBackend.cpp b/src/backend/opengl/OpenGLBackend.cpp
index b2229c6..348d03d 100644
--- a/src/backend/opengl/OpenGLBackend.cpp
+++ b/src/backend/opengl/OpenGLBackend.cpp
@@ -15,6 +15,7 @@
 #include "OpenGLBackend.h"
 
 #include "CommandBufferGL.h"
+#include "DepthStencilStateGL.h"
 #include "PipelineGL.h"
 #include "PipelineLayoutGL.h"
 #include "ShaderModuleGL.h"
@@ -45,52 +46,6 @@
         *device = reinterpret_cast<nxtDevice>(new Device);
     }
 
-    GLuint OpenGLCompareFunction(nxt::CompareFunction compareFunction) {
-        switch (compareFunction) {
-            case nxt::CompareFunction::Never:
-                return GL_NEVER;
-            case nxt::CompareFunction::Less:
-                return GL_LESS;
-            case nxt::CompareFunction::LessEqual:
-                return GL_LEQUAL;
-            case nxt::CompareFunction::Greater:
-                return GL_GREATER;
-            case nxt::CompareFunction::GreaterEqual:
-                return GL_GEQUAL;
-            case nxt::CompareFunction::NotEqual:
-                return GL_NOTEQUAL;
-            case nxt::CompareFunction::Equal:
-                return GL_EQUAL;
-            case nxt::CompareFunction::Always:
-                return GL_ALWAYS;
-            default:
-                ASSERT(false);
-        }
-    }
-
-    GLuint OpenGLStencilOperation(nxt::StencilOperation stencilOperation) {
-        switch (stencilOperation) {
-            case nxt::StencilOperation::Keep:
-                return GL_KEEP;
-            case nxt::StencilOperation::Zero:
-                return GL_ZERO;
-            case nxt::StencilOperation::Replace:
-                return GL_REPLACE;
-            case nxt::StencilOperation::Invert:
-                return GL_INVERT;
-            case nxt::StencilOperation::IncrementClamp:
-                return GL_INCR;
-            case nxt::StencilOperation::DecrementClamp:
-                return GL_DECR;
-            case nxt::StencilOperation::IncrementWrap:
-                return GL_INCR_WRAP;
-            case nxt::StencilOperation::DecrementWrap:
-                return GL_DECR_WRAP;
-            default:
-                ASSERT(false);
-        }
-    }
-
     // Device
 
     BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
@@ -184,12 +139,6 @@
         : BufferViewBase(builder), device(device) {
     }
 
-    // DepthStencilState
-
-    DepthStencilState::DepthStencilState(Device* device, DepthStencilStateBuilder* builder)
-        : DepthStencilStateBase(builder), device(device) {
-    }
-
     // InputState
 
     InputState::InputState(Device* device, InputStateBuilder* builder)
diff --git a/src/backend/opengl/OpenGLBackend.h b/src/backend/opengl/OpenGLBackend.h
index 7ef6d85..c1d0659 100644
--- a/src/backend/opengl/OpenGLBackend.h
+++ b/src/backend/opengl/OpenGLBackend.h
@@ -75,9 +75,6 @@
         return ToBackendBase<OpenGLBackendTraits>(common);
     }
 
-    GLuint OpenGLCompareFunction(nxt::CompareFunction compareFunction);
-    GLuint OpenGLStencilOperation(nxt::StencilOperation stencilOperation);
-
     // Definition of backend types
     class Device : public DeviceBase {
         public:
@@ -140,14 +137,6 @@
             Device* device;
     };
 
-    class DepthStencilState : public DepthStencilStateBase {
-        public:
-            DepthStencilState(Device* device, DepthStencilStateBuilder* builder);
-
-        private:
-            Device* device;
-    };
-
     class Framebuffer : public FramebufferBase {
         public:
             Framebuffer(Device* device, FramebufferBuilder* builder);
diff --git a/src/backend/opengl/PersistentPipelineStateGL.cpp b/src/backend/opengl/PersistentPipelineStateGL.cpp
index 64b55cb..a6ee85e 100644
--- a/src/backend/opengl/PersistentPipelineStateGL.cpp
+++ b/src/backend/opengl/PersistentPipelineStateGL.cpp
@@ -19,151 +19,38 @@
 namespace backend {
 namespace opengl {
 
-    PersistentPipelineState::PersistentPipelineState() {
-        dirtyFields.set(); // initialize all fields as dirty
+    void PersistentPipelineState::SetDefaultState() {
+        stencilBackCompareFunction = GL_ALWAYS;
+        stencilFrontCompareFunction = GL_ALWAYS;
+        stencilReadMask = 0xff;
+        SetStencilReference(0);
     }
 
-    // when a field on PersistentPipelineState::State changes, mark its starting location as dirty
-    #define SET_FIELD(field, destination, value) { \
-        if (state.destination != value) { \
-            dirtyFields.set(field); \
-            state.destination = value; \
-        } \
+    void PersistentPipelineState::CacheStencilFuncsAndMask(GLenum stencilBackCompareFunction, GLenum stencilFrontCompareFunction, uint32_t stencilReadMask) {
+        this->stencilBackCompareFunction = stencilBackCompareFunction;
+        this->stencilFrontCompareFunction = stencilFrontCompareFunction;
+        this->stencilReadMask = stencilReadMask;
     }
 
-    void PersistentPipelineState::UpdateDepthStencilInfo(const DepthStencilStateBase* const depthStencilState) {
-        auto& depth = depthStencilState->GetDepth();
-        SET_FIELD(DEPTH_COMPARE_FUNCTION, depthInfo.compareFunction, depth.compareFunction)
-        SET_FIELD(DEPTH_WRITE_ENABLED, depthInfo.depthWriteEnabled, depth.depthWriteEnabled)
-        SET_FIELD(DEPTH_ENABLED, depthEnabled, depthStencilState->DepthTestEnabled())
-
-        auto& stencil = depthStencilState->GetStencil();
-        SET_FIELD(STENCIL_ENABLED, stencilEnabled, depthStencilState->StencilTestEnabled())
-
-        SET_FIELD(STENCIL_BACK_COMPARE_FUNCTION, stencilInfo.back.compareFunction, stencil.back.compareFunction)
-        SET_FIELD(STENCIL_BACK_STENCIL_FAIL, stencilInfo.back.stencilFail, stencil.back.stencilFail)
-        SET_FIELD(STENCIL_BACK_DEPTH_FAIL, stencilInfo.back.depthFail, stencil.back.depthFail)
-        SET_FIELD(STENCIL_BACK_DEPTH_STENCIL_PASS, stencilInfo.back.depthStencilPass, stencil.back.depthStencilPass)
-        SET_FIELD(STENCIL_BACK_MASK, stencilInfo.back.mask, stencil.back.mask)
-
-        SET_FIELD(STENCIL_FRONT_COMPARE_FUNCTION, stencilInfo.front.compareFunction, stencil.front.compareFunction)
-        SET_FIELD(STENCIL_FRONT_STENCIL_FAIL, stencilInfo.front.stencilFail, stencil.front.stencilFail)
-        SET_FIELD(STENCIL_FRONT_DEPTH_FAIL, stencilInfo.front.depthFail, stencil.front.depthFail)
-        SET_FIELD(STENCIL_FRONT_DEPTH_STENCIL_PASS, stencilInfo.front.depthStencilPass, stencil.front.depthStencilPass)
-        SET_FIELD(STENCIL_FRONT_MASK, stencilInfo.front.mask, stencil.front.mask)
-    }
-
-    void PersistentPipelineState::UpdateStencilReference(uint32_t stencilReference) {
-        SET_FIELD(STENCIL_REFERENCE, stencilReference, stencilReference)
-    }
-
-    #undef SET_FIELD
-
-    bool PersistentPipelineState::IsDirty(Field field) const {
-        return dirtyFields.test(field);
-    }
-
-    void PersistentPipelineState::CleanField(Field field) {
-        dirtyFields.reset(field);
-    }
-
-    void PersistentPipelineState::ApplyDepthNow() {
-        if (IsDirty(DEPTH_ENABLED)) {
-            if (state.depthEnabled) {
-                glEnable(GL_DEPTH_TEST);
-            } else {
-                glDisable(GL_DEPTH_TEST);
-            }
-            CleanField(DEPTH_ENABLED);
-        }
-        if (IsDirty(DEPTH_WRITE_ENABLED)) {
-            if (state.depthInfo.depthWriteEnabled) {
-                glDepthMask(GL_TRUE);
-            } else {
-                glDepthMask(GL_FALSE);
-            }
-            CleanField(DEPTH_WRITE_ENABLED);
-        }
-        if (IsDirty(DEPTH_COMPARE_FUNCTION)) {
-            glDepthFunc(OpenGLCompareFunction(state.depthInfo.compareFunction));
-            CleanField(DEPTH_COMPARE_FUNCTION);
-        }
-    }
-
-    void PersistentPipelineState::ApplyStencilNow() {
-        if (IsDirty(STENCIL_ENABLED)) {
-            if (state.stencilEnabled) {
-                glEnable(GL_STENCIL_TEST);
-            } else {
-                glDisable(GL_STENCIL_TEST);
-            }
-            CleanField(STENCIL_ENABLED);
-        }
-
-        if (IsDirty(STENCIL_BACK_STENCIL_FAIL) ||
-            IsDirty(STENCIL_BACK_DEPTH_FAIL) ||
-            IsDirty(STENCIL_BACK_DEPTH_STENCIL_PASS)) {
-
-            glStencilOpSeparate(GL_BACK,
-                OpenGLStencilOperation(state.stencilInfo.back.stencilFail),
-                OpenGLStencilOperation(state.stencilInfo.back.depthFail),
-                OpenGLStencilOperation(state.stencilInfo.back.depthStencilPass)
-            );
-
-            CleanField(STENCIL_BACK_STENCIL_FAIL);
-            CleanField(STENCIL_BACK_DEPTH_FAIL);
-            CleanField(STENCIL_BACK_DEPTH_STENCIL_PASS);
-        }
-        if (IsDirty(STENCIL_BACK_COMPARE_FUNCTION) ||
-            IsDirty(STENCIL_REFERENCE) ||
-            IsDirty(STENCIL_BACK_MASK)) {
-
+    void PersistentPipelineState::SetStencilReference(uint32_t stencilReference) {
+        if (this->stencilReference != stencilReference) {
+            this->stencilReference = stencilReference;
             glStencilFuncSeparate(GL_BACK,
-                OpenGLCompareFunction(state.stencilInfo.back.compareFunction),
-                state.stencilReference,
-                state.stencilInfo.back.mask
+                stencilBackCompareFunction,
+                stencilReference,
+                stencilReadMask
             );
-            if (IsDirty(STENCIL_BACK_MASK)) {
-                glStencilMaskSeparate(GL_BACK, state.stencilInfo.back.mask);
-            }
-
-            CleanField(STENCIL_BACK_COMPARE_FUNCTION);
-            CleanField(STENCIL_BACK_MASK);
-        }
-
-        if (IsDirty(STENCIL_FRONT_STENCIL_FAIL) ||
-            IsDirty(STENCIL_FRONT_DEPTH_FAIL) ||
-            IsDirty(STENCIL_FRONT_DEPTH_STENCIL_PASS)) {
-
-            glStencilOpSeparate(GL_FRONT,
-                OpenGLStencilOperation(state.stencilInfo.front.stencilFail),
-                OpenGLStencilOperation(state.stencilInfo.front.depthFail),
-                OpenGLStencilOperation(state.stencilInfo.front.depthStencilPass)
-            );
-
-            CleanField(STENCIL_FRONT_STENCIL_FAIL);
-            CleanField(STENCIL_FRONT_DEPTH_FAIL);
-            CleanField(STENCIL_FRONT_DEPTH_STENCIL_PASS);
-        }
-        if (IsDirty(STENCIL_FRONT_COMPARE_FUNCTION) ||
-            IsDirty(STENCIL_REFERENCE) ||
-            IsDirty(STENCIL_FRONT_MASK)) {
-
             glStencilFuncSeparate(GL_FRONT,
-                OpenGLCompareFunction(state.stencilInfo.front.compareFunction),
-                state.stencilReference,
-                state.stencilInfo.front.mask
+                stencilFrontCompareFunction,
+                stencilReference,
+                stencilReadMask
             );
-            if (IsDirty(STENCIL_FRONT_MASK)) {
-                glStencilMaskSeparate(GL_FRONT, state.stencilInfo.front.mask);
-            }
-
-            CleanField(STENCIL_FRONT_COMPARE_FUNCTION);
-            CleanField(STENCIL_FRONT_MASK);
         }
+    }
 
-        CleanField(STENCIL_REFERENCE); // clean this last because its used for both the back and front functions
+    GLuint PersistentPipelineState::GetCachedStencilReference() const {
+        return stencilReference;
     }
 
 }
-}
\ No newline at end of file
+}
diff --git a/src/backend/opengl/PersistentPipelineStateGL.h b/src/backend/opengl/PersistentPipelineStateGL.h
index 37e7d38..1381fdc 100644
--- a/src/backend/opengl/PersistentPipelineStateGL.h
+++ b/src/backend/opengl/PersistentPipelineStateGL.h
@@ -15,58 +15,28 @@
 #ifndef BACKEND_OPENGL_PERSISTENTPIPELINESTATE_H_
 #define BACKEND_OPENGL_PERSISTENTPIPELINESTATE_H_
 
-#include "common/DepthStencilState.h"
+#include "nxt/nxtcpp.h"
 
-#include <bitset>
+#include "glad/glad.h"
 
 namespace backend {
 namespace opengl {
 
     class PersistentPipelineState {
         public:
-            PersistentPipelineState();
-            void UpdateDepthStencilInfo(const DepthStencilStateBase* const depthStencilState);
-            void UpdateStencilReference(uint32_t stencilReference);
-
-            void ApplyDepthNow();
-            void ApplyStencilNow();
-
-            enum Field {
-                DEPTH_COMPARE_FUNCTION,
-                DEPTH_WRITE_ENABLED,
-                DEPTH_ENABLED,
-                STENCIL_ENABLED,
-                STENCIL_BACK_COMPARE_FUNCTION,
-                STENCIL_BACK_STENCIL_FAIL,
-                STENCIL_BACK_DEPTH_FAIL,
-                STENCIL_BACK_DEPTH_STENCIL_PASS,
-                STENCIL_BACK_MASK,
-                STENCIL_FRONT_COMPARE_FUNCTION,
-                STENCIL_FRONT_STENCIL_FAIL,
-                STENCIL_FRONT_DEPTH_FAIL,
-                STENCIL_FRONT_DEPTH_STENCIL_PASS,
-                STENCIL_FRONT_MASK,
-                STENCIL_REFERENCE,
-                Count
-            };
-
-            struct State {
-                bool depthEnabled;
-                bool stencilEnabled;
-                DepthStencilStateBase::DepthInfo depthInfo;
-                DepthStencilStateBase::StencilInfo stencilInfo;
-                uint32_t stencilReference;
-            };
+            void SetDefaultState();
+            void CacheStencilFuncsAndMask(GLenum stencilBackCompareFunction, GLenum stencilFrontCompareFunction, uint32_t stencilReadMask);
+            void SetStencilReference(uint32_t stencilReference);
+            GLuint GetCachedStencilReference() const;
 
         private:
-            State state;
-            std::bitset<Field::Count> dirtyFields;
-
-            inline bool IsDirty(Field field) const;
-            inline void CleanField(Field field);
+            GLenum stencilBackCompareFunction;
+            GLenum stencilFrontCompareFunction;
+            GLuint stencilReadMask;
+            GLuint stencilReference;
     };
 
 }
 }
 
-#endif // BACKEND_OPENGL_PERSISTENTPIPELINESTATE_H_
\ No newline at end of file
+#endif // BACKEND_OPENGL_PERSISTENTPIPELINESTATE_H_
diff --git a/src/backend/opengl/PipelineGL.cpp b/src/backend/opengl/PipelineGL.cpp
index 0fcfc3a..b27596a 100644
--- a/src/backend/opengl/PipelineGL.cpp
+++ b/src/backend/opengl/PipelineGL.cpp
@@ -14,6 +14,7 @@
 
 #include "PipelineGL.h"
 
+#include "DepthStencilStateGL.h"
 #include "OpenGLBackend.h"
 #include "PersistentPipelineStateGL.h"
 #include "PipelineLayoutGL.h"
@@ -209,9 +210,8 @@
         auto inputState = ToBackend(GetInputState());
         glBindVertexArray(inputState->GetVAO());
 
-        persistentPipelineState.UpdateDepthStencilInfo(GetDepthStencilState());
-        persistentPipelineState.ApplyDepthNow();
-        persistentPipelineState.ApplyStencilNow();
+        auto depthStencilState = ToBackend(GetDepthStencilState());
+        depthStencilState->ApplyNow(persistentPipelineState);
     }
 
 }
diff --git a/src/tests/unittests/validation/DepthStencilStateValidationTest.cpp b/src/tests/unittests/validation/DepthStencilStateValidationTest.cpp
index c7b6c40..9cff8ae 100644
--- a/src/tests/unittests/validation/DepthStencilStateValidationTest.cpp
+++ b/src/tests/unittests/validation/DepthStencilStateValidationTest.cpp
@@ -17,66 +17,79 @@
 class DepthStencilStateValidationTest : public ValidationTest {
 };
 
-TEST_F(DepthStencilStateValidationTest, Creation) {
-    // Success
-    nxt::DepthStencilState buf0 = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
-        .SetDepthCompareFunction(nxt::CompareFunction::Less)
-        .SetDepthWriteEnabled(true)
-        .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Greater,
-            nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace)
-        .SetStencilMask(nxt::Face::Both, 0x1)
-        .GetResult();
+TEST_F(DepthStencilStateValidationTest, CreationSuccess) {
+    // Success for setting all properties
+    {
+        nxt::DepthStencilState ds = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
+            .SetDepthCompareFunction(nxt::CompareFunction::Less)
+            .SetDepthWriteEnabled(true)
+            .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Greater,
+                nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace)
+            .SetStencilMask(0x0, 0x1)
+            .GetResult();
+    }
 
     // Success for empty builder
-    nxt::DepthStencilState buf1 = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
-        .GetResult();
+    {
+        nxt::DepthStencilState ds = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
+            .GetResult();
+    }
 
-    // Test failure when specifying properties multiple times
-    nxt::DepthStencilState buf2 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetDepthWriteEnabled(true)
-        .SetDepthWriteEnabled(false)
-        .GetResult();
-
-    nxt::DepthStencilState buf3 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetDepthCompareFunction(nxt::CompareFunction::Less)
-        .SetDepthCompareFunction(nxt::CompareFunction::Greater)
-        .GetResult();
-
-    // Test success when setting properties on separate faces
-    nxt::DepthStencilState buf4 = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
-        .SetStencilMask(nxt::Face::Front, 0x00)
-        .SetStencilMask(nxt::Face::Back, 0xff)
-        .GetResult();
-
-    nxt::DepthStencilState buf5 = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
-        .SetStencilFunction(nxt::Face::Front, nxt::CompareFunction::Less,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .GetResult();
-
-    // Test failure when setting properties on a face multiple times
-    nxt::DepthStencilState buf6 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetStencilMask(nxt::Face::Back, 0x00)
-        .SetStencilMask(nxt::Face::Back, 0xff)
-        .GetResult();
-
-    nxt::DepthStencilState buf7 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Less,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .GetResult();
-
-    nxt::DepthStencilState buf8 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetStencilMask(nxt::Face::Both, 0x00)
-        .SetStencilMask(nxt::Face::Back, 0xff)
-        .GetResult();
-
-    nxt::DepthStencilState buf9 = AssertWillBeError(device.CreateDepthStencilStateBuilder())
-        .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Less,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
-            nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
-        .GetResult();
+    // Test success when setting stencil function on separate faces
+    {
+        nxt::DepthStencilState ds = AssertWillBeSuccess(device.CreateDepthStencilStateBuilder())
+            .SetStencilFunction(nxt::Face::Front, nxt::CompareFunction::Less,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .GetResult();
+    }
 }
+
+TEST_F(DepthStencilStateValidationTest, CreationDuplicates) {
+    // Test failure when specifying depth write enabled multiple times
+    {
+        nxt::DepthStencilState ds = AssertWillBeError(device.CreateDepthStencilStateBuilder())
+            .SetDepthWriteEnabled(true)
+            .SetDepthWriteEnabled(false)
+            .GetResult();
+    }
+
+    // Test failure when specifying depth compare function multiple times
+    {
+        nxt::DepthStencilState ds = AssertWillBeError(device.CreateDepthStencilStateBuilder())
+            .SetDepthCompareFunction(nxt::CompareFunction::Less)
+            .SetDepthCompareFunction(nxt::CompareFunction::Greater)
+            .GetResult();
+    }
+
+    // Test failure when setting stencil mask  multiple times
+    {
+        nxt::DepthStencilState ds = AssertWillBeError(device.CreateDepthStencilStateBuilder())
+            .SetStencilMask(0x00, 0x00)
+            .SetStencilMask(0xff, 0xff)
+            .GetResult();
+    }
+
+    // Test failure when directly setting stencil function on a face multiple times
+    {
+        nxt::DepthStencilState ds = AssertWillBeError(device.CreateDepthStencilStateBuilder())
+            .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Less,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .GetResult();
+    }
+
+    // Test failure when indirectly setting stencil function on a face multiple times
+    {
+        nxt::DepthStencilState ds = AssertWillBeError(device.CreateDepthStencilStateBuilder())
+            .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Less,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .SetStencilFunction(nxt::Face::Back, nxt::CompareFunction::Greater,
+                nxt::StencilOperation::Replace, nxt::StencilOperation::Replace, nxt::StencilOperation::Replace)
+            .GetResult();
+    }
+}
+
+// TODO(enga@google.com) Test failure when set in a compute pipeline