Deduplicate AttachmentState shared by RenderPipeline and RenderPasses

This both deduplicates shared state by multiple passes or pipelines and
makes checking pipeline compatibility a single pointer check. It will be
useful for also checking RenderBundle compatibility.

Bug: dawn:154
Change-Id: I0fb289fab5ac76a7fbd500f64b8a6409a246ab32
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9461
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 513c74e..45b2381 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -104,6 +104,8 @@
   sources += [
     "src/dawn_native/Adapter.cpp",
     "src/dawn_native/Adapter.h",
+    "src/dawn_native/AttachmentState.cpp",
+    "src/dawn_native/AttachmentState.h",
     "src/dawn_native/BackendConnection.cpp",
     "src/dawn_native/BackendConnection.h",
     "src/dawn_native/BindGroup.cpp",
diff --git a/src/dawn_native/AttachmentState.cpp b/src/dawn_native/AttachmentState.cpp
new file mode 100644
index 0000000..39ddb4b
--- /dev/null
+++ b/src/dawn_native/AttachmentState.cpp
@@ -0,0 +1,146 @@
+// 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 "dawn_native/AttachmentState.h"
+
+#include "common/BitSetIterator.h"
+#include "common/HashUtils.h"
+#include "dawn_native/Device.h"
+#include "dawn_native/Texture.h"
+
+namespace dawn_native {
+
+    AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor)
+        : mHasDepthStencilAttachment(descriptor->depthStencilState != nullptr),
+          mSampleCount(descriptor->sampleCount) {
+        for (uint32_t i = 0; i < descriptor->colorStateCount; ++i) {
+            ASSERT(descriptor->colorStates[i] != nullptr);
+            mColorAttachmentsSet.set(i);
+            mColorFormats[i] = descriptor->colorStates[i]->format;
+        }
+        if (mHasDepthStencilAttachment) {
+            mDepthStencilFormat = descriptor->depthStencilState->format;
+        }
+    }
+
+    AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPassDescriptor* descriptor)
+        : mHasDepthStencilAttachment(descriptor->depthStencilAttachment != nullptr) {
+        for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
+            TextureViewBase* attachment = descriptor->colorAttachments[i]->attachment;
+            mColorAttachmentsSet.set(i);
+            mColorFormats[i] = attachment->GetFormat().format;
+            if (mSampleCount == 0) {
+                mSampleCount = attachment->GetTexture()->GetSampleCount();
+            } else {
+                ASSERT(mSampleCount == attachment->GetTexture()->GetSampleCount());
+            }
+        }
+        if (mHasDepthStencilAttachment) {
+            TextureViewBase* attachment = descriptor->depthStencilAttachment->attachment;
+            mDepthStencilFormat = attachment->GetFormat().format;
+            if (mSampleCount == 0) {
+                mSampleCount = attachment->GetTexture()->GetSampleCount();
+            } else {
+                ASSERT(mSampleCount == attachment->GetTexture()->GetSampleCount());
+            }
+        }
+        ASSERT(mSampleCount > 0);
+    }
+
+    AttachmentStateBlueprint::AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs) =
+        default;
+
+    size_t AttachmentStateBlueprint::HashFunc::operator()(
+        const AttachmentStateBlueprint* attachmentState) const {
+        size_t hash = 0;
+
+        // Hash color formats
+        HashCombine(&hash, attachmentState->mColorAttachmentsSet);
+        for (uint32_t i : IterateBitSet(attachmentState->mColorAttachmentsSet)) {
+            HashCombine(&hash, attachmentState->mColorFormats[i]);
+        }
+
+        // Hash depth stencil attachments
+        if (attachmentState->mHasDepthStencilAttachment) {
+            HashCombine(&hash, attachmentState->mDepthStencilFormat);
+        }
+
+        // Hash sample count
+        HashCombine(&hash, attachmentState->mSampleCount);
+
+        return hash;
+    }
+
+    bool AttachmentStateBlueprint::EqualityFunc::operator()(
+        const AttachmentStateBlueprint* a,
+        const AttachmentStateBlueprint* b) const {
+        // Check set attachments
+        if (a->mColorAttachmentsSet != b->mColorAttachmentsSet ||
+            a->mHasDepthStencilAttachment != b->mHasDepthStencilAttachment) {
+            return false;
+        }
+
+        // Check color formats
+        for (uint32_t i : IterateBitSet(a->mColorAttachmentsSet)) {
+            if (a->mColorFormats[i] != b->mColorFormats[i]) {
+                return false;
+            }
+        }
+
+        // Check depth stencil format
+        if (a->mHasDepthStencilAttachment) {
+            if (a->mDepthStencilFormat != b->mDepthStencilFormat) {
+                return false;
+            }
+        }
+
+        // Check sample count
+        if (a->mSampleCount != b->mSampleCount) {
+            return false;
+        }
+
+        return true;
+    }
+
+    AttachmentState::AttachmentState(DeviceBase* device, const AttachmentStateBlueprint& blueprint)
+        : AttachmentStateBlueprint(blueprint), RefCounted(), mDevice(device) {
+    }
+
+    AttachmentState::~AttachmentState() {
+        mDevice->UncacheAttachmentState(this);
+    }
+
+    std::bitset<kMaxColorAttachments> AttachmentState::GetColorAttachmentsMask() const {
+        return mColorAttachmentsSet;
+    }
+
+    dawn::TextureFormat AttachmentState::GetColorAttachmentFormat(uint32_t index) const {
+        ASSERT(mColorAttachmentsSet[index]);
+        return mColorFormats[index];
+    }
+
+    bool AttachmentState::HasDepthStencilAttachment() const {
+        return mHasDepthStencilAttachment;
+    }
+
+    dawn::TextureFormat AttachmentState::GetDepthStencilFormat() const {
+        ASSERT(mHasDepthStencilAttachment);
+        return mDepthStencilFormat;
+    }
+
+    uint32_t AttachmentState::GetSampleCount() const {
+        return mSampleCount;
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/AttachmentState.h b/src/dawn_native/AttachmentState.h
new file mode 100644
index 0000000..34f2c1a
--- /dev/null
+++ b/src/dawn_native/AttachmentState.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef DAWNNATIVE_ATTACHMENTSTATE_H_
+#define DAWNNATIVE_ATTACHMENTSTATE_H_
+
+#include "common/Constants.h"
+#include "dawn_native/RefCounted.h"
+
+#include "dawn_native/dawn_platform.h"
+
+#include <array>
+#include <bitset>
+
+namespace dawn_native {
+
+    class DeviceBase;
+
+    // AttachmentStateBlueprint and AttachmentState are separated so the AttachmentState
+    // can be constructed by copying the blueprint state instead of traversing descriptors.
+    // Also, AttachmentStateBlueprint does not need a refcount like AttachmentState.
+    class AttachmentStateBlueprint {
+      public:
+        // Note: Descriptors must be validated before the AttachmentState is constructed.
+        AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor);
+        AttachmentStateBlueprint(const RenderPassDescriptor* descriptor);
+
+        AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs);
+
+        // Functors necessary for the unordered_set<AttachmentState*>-based cache.
+        struct HashFunc {
+            size_t operator()(const AttachmentStateBlueprint* attachmentState) const;
+        };
+        struct EqualityFunc {
+            bool operator()(const AttachmentStateBlueprint* a,
+                            const AttachmentStateBlueprint* b) const;
+        };
+
+      protected:
+        std::bitset<kMaxColorAttachments> mColorAttachmentsSet;
+        std::array<dawn::TextureFormat, kMaxColorAttachments> mColorFormats;
+        bool mHasDepthStencilAttachment = false;
+        dawn::TextureFormat mDepthStencilFormat;
+        uint32_t mSampleCount = 0;
+    };
+
+    class AttachmentState : public AttachmentStateBlueprint, public RefCounted {
+      public:
+        AttachmentState(DeviceBase* device, const AttachmentStateBlueprint& blueprint);
+        ~AttachmentState() override;
+
+        std::bitset<kMaxColorAttachments> GetColorAttachmentsMask() const;
+        dawn::TextureFormat GetColorAttachmentFormat(uint32_t index) const;
+        bool HasDepthStencilAttachment() const;
+        dawn::TextureFormat GetDepthStencilFormat() const;
+        uint32_t GetSampleCount() const;
+
+      private:
+        DeviceBase* mDevice;
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_ATTACHMENTSTATE_H_
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index c5c351a..be33223 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -678,22 +678,19 @@
                 BeginRenderPassCmd* cmd =
                     allocator->Allocate<BeginRenderPassCmd>(Command::BeginRenderPass);
 
-                for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
-                    if (descriptor->colorAttachments[i] != nullptr) {
-                        cmd->colorAttachmentsSet.set(i);
-                        cmd->colorAttachments[i].view = descriptor->colorAttachments[i]->attachment;
-                        cmd->colorAttachments[i].resolveTarget =
-                            descriptor->colorAttachments[i]->resolveTarget;
-                        cmd->colorAttachments[i].loadOp = descriptor->colorAttachments[i]->loadOp;
-                        cmd->colorAttachments[i].storeOp = descriptor->colorAttachments[i]->storeOp;
-                        cmd->colorAttachments[i].clearColor =
-                            descriptor->colorAttachments[i]->clearColor;
-                    }
+                cmd->attachmentState = device->GetOrCreateAttachmentState(descriptor);
+
+                for (uint32_t i : IterateBitSet(cmd->attachmentState->GetColorAttachmentsMask())) {
+                    cmd->colorAttachments[i].view = descriptor->colorAttachments[i]->attachment;
+                    cmd->colorAttachments[i].resolveTarget =
+                        descriptor->colorAttachments[i]->resolveTarget;
+                    cmd->colorAttachments[i].loadOp = descriptor->colorAttachments[i]->loadOp;
+                    cmd->colorAttachments[i].storeOp = descriptor->colorAttachments[i]->storeOp;
+                    cmd->colorAttachments[i].clearColor =
+                        descriptor->colorAttachments[i]->clearColor;
                 }
 
-                cmd->hasDepthStencilAttachment = descriptor->depthStencilAttachment != nullptr;
-                if (cmd->hasDepthStencilAttachment) {
-                    cmd->hasDepthStencilAttachment = true;
+                if (cmd->attachmentState->HasDepthStencilAttachment()) {
                     cmd->depthStencilAttachment.view =
                         descriptor->depthStencilAttachment->attachment;
                     cmd->depthStencilAttachment.clearDepth =
@@ -712,7 +709,6 @@
 
                 cmd->width = width;
                 cmd->height = height;
-                cmd->sampleCount = sampleCount;
 
                 return {};
             });
@@ -1071,7 +1067,7 @@
         CommandBufferStateTracker persistentState;
 
         // Track usage of the render pass attachments
-        for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+        for (uint32_t i : IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
             RenderPassColorAttachmentInfo* colorAttachment = &renderPass->colorAttachments[i];
             TextureBase* texture = colorAttachment->view->GetTexture();
             usageTracker.TextureUsedAs(texture, dawn::TextureUsageBit::OutputAttachment);
@@ -1083,7 +1079,7 @@
             }
         }
 
-        if (renderPass->hasDepthStencilAttachment) {
+        if (renderPass->attachmentState->HasDepthStencilAttachment()) {
             TextureBase* texture = renderPass->depthStencilAttachment.view->GetTexture();
             usageTracker.TextureUsedAs(texture, dawn::TextureUsageBit::OutputAttachment);
         }
diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h
index 24e4e11..442cc95 100644
--- a/src/dawn_native/Commands.h
+++ b/src/dawn_native/Commands.h
@@ -17,6 +17,7 @@
 
 #include "common/Constants.h"
 
+#include "dawn_native/AttachmentState.h"
 #include "dawn_native/Texture.h"
 
 #include "dawn_native/dawn_platform.h"
@@ -80,15 +81,13 @@
     };
 
     struct BeginRenderPassCmd {
-        std::bitset<kMaxColorAttachments> colorAttachmentsSet;
+        Ref<AttachmentState> attachmentState;
         RenderPassColorAttachmentInfo colorAttachments[kMaxColorAttachments];
-        bool hasDepthStencilAttachment;
         RenderPassDepthStencilAttachmentInfo depthStencilAttachment;
 
-        // Cache the width, height and sample count of all attachments for convenience
+        // Cache the width and height of all attachments for convenience
         uint32_t width;
         uint32_t height;
-        uint32_t sampleCount;
     };
 
     struct BufferCopy {
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 0901c7d..d32f02ab 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -15,6 +15,7 @@
 #include "dawn_native/Device.h"
 
 #include "dawn_native/Adapter.h"
+#include "dawn_native/AttachmentState.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Buffer.h"
@@ -47,6 +48,7 @@
         std::unordered_set<Object*, typename Object::HashFunc, typename Object::EqualityFunc>;
 
     struct DeviceBase::Caches {
+        ContentLessObjectCache<AttachmentStateBlueprint> attachmentStates;
         ContentLessObjectCache<BindGroupLayoutBase> bindGroupLayouts;
         ContentLessObjectCache<ComputePipelineBase> computePipelines;
         ContentLessObjectCache<PipelineLayoutBase> pipelineLayouts;
@@ -249,6 +251,43 @@
         ASSERT(removedCount == 1);
     }
 
+    AttachmentState* DeviceBase::GetOrCreateAttachmentState(
+        const RenderPipelineDescriptor* descriptor) {
+        AttachmentStateBlueprint blueprint(descriptor);
+
+        auto iter = mCaches->attachmentStates.find(&blueprint);
+        if (iter != mCaches->attachmentStates.end()) {
+            AttachmentState* cachedState = static_cast<AttachmentState*>(*iter);
+            cachedState->Reference();
+            return cachedState;
+        }
+
+        AttachmentState* attachmentState = new AttachmentState(this, blueprint);
+        mCaches->attachmentStates.insert(attachmentState);
+        return attachmentState;
+    }
+
+    AttachmentState* DeviceBase::GetOrCreateAttachmentState(
+        const RenderPassDescriptor* descriptor) {
+        AttachmentStateBlueprint blueprint(descriptor);
+
+        auto iter = mCaches->attachmentStates.find(&blueprint);
+        if (iter != mCaches->attachmentStates.end()) {
+            AttachmentState* cachedState = static_cast<AttachmentState*>(*iter);
+            cachedState->Reference();
+            return cachedState;
+        }
+
+        AttachmentState* attachmentState = new AttachmentState(this, blueprint);
+        mCaches->attachmentStates.insert(attachmentState);
+        return attachmentState;
+    }
+
+    void DeviceBase::UncacheAttachmentState(AttachmentState* obj) {
+        size_t removedCount = mCaches->attachmentStates.erase(obj);
+        ASSERT(removedCount == 1);
+    }
+
     // Object creation API methods
 
     BindGroupBase* DeviceBase::CreateBindGroup(const BindGroupDescriptor* descriptor) {
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index ad99a8f..3c3d6c7 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -32,6 +32,7 @@
     using ErrorCallback = void (*)(const char* errorMessage, void* userData);
 
     class AdapterBase;
+    class AttachmentState;
     class FenceSignalTracker;
     class DynamicUploader;
     class StagingBufferBase;
@@ -113,6 +114,10 @@
             const ShaderModuleDescriptor* descriptor);
         void UncacheShaderModule(ShaderModuleBase* obj);
 
+        AttachmentState* GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor);
+        AttachmentState* GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor);
+        void UncacheAttachmentState(AttachmentState* obj);
+
         // Dawn API
         BindGroupBase* CreateBindGroup(const BindGroupDescriptor* descriptor);
         BindGroupLayoutBase* CreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor);
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index 421abf2..db022c7 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -349,9 +349,8 @@
                        descriptor->layout,
                        dawn::ShaderStageBit::Vertex | dawn::ShaderStageBit::Fragment),
           mVertexInput(*descriptor->vertexInput),
-          mHasDepthStencilAttachment(descriptor->depthStencilState != nullptr),
+          mAttachmentState(device->GetOrCreateAttachmentState(descriptor)),
           mPrimitiveTopology(descriptor->primitiveTopology),
-          mSampleCount(descriptor->sampleCount),
           mSampleMask(descriptor->sampleMask),
           mAlphaToCoverageEnabled(descriptor->alphaToCoverageEnabled),
           mVertexModule(descriptor->vertexStage->module),
@@ -385,7 +384,7 @@
             mRasterizationState = RasterizationStateDescriptor();
         }
 
-        if (mHasDepthStencilAttachment) {
+        if (mAttachmentState->HasDepthStencilAttachment()) {
             mDepthStencilState = *descriptor->depthStencilState;
         } else {
             // These default values below are useful for backends to fill information.
@@ -406,8 +405,7 @@
             mDepthStencilState.stencilWriteMask = 0xff;
         }
 
-        for (uint32_t i = 0; i < descriptor->colorStateCount; ++i) {
-            mColorAttachmentsSet.set(i);
+        for (uint32_t i : IterateBitSet(mAttachmentState->GetColorAttachmentsMask())) {
             mColorStates[i] = *descriptor->colorStates[i];
         }
 
@@ -487,12 +485,12 @@
 
     std::bitset<kMaxColorAttachments> RenderPipelineBase::GetColorAttachmentsMask() const {
         ASSERT(!IsError());
-        return mColorAttachmentsSet;
+        return mAttachmentState->GetColorAttachmentsMask();
     }
 
     bool RenderPipelineBase::HasDepthStencilAttachment() const {
         ASSERT(!IsError());
-        return mHasDepthStencilAttachment;
+        return mAttachmentState->HasDepthStencilAttachment();
     }
 
     dawn::TextureFormat RenderPipelineBase::GetColorAttachmentFormat(uint32_t attachment) const {
@@ -502,49 +500,22 @@
 
     dawn::TextureFormat RenderPipelineBase::GetDepthStencilFormat() const {
         ASSERT(!IsError());
-        ASSERT(mHasDepthStencilAttachment);
+        ASSERT(mAttachmentState->HasDepthStencilAttachment());
         return mDepthStencilState.format;
     }
 
     uint32_t RenderPipelineBase::GetSampleCount() const {
         ASSERT(!IsError());
-        return mSampleCount;
+        return mAttachmentState->GetSampleCount();
     }
 
     MaybeError RenderPipelineBase::ValidateCompatibleWith(
         const BeginRenderPassCmd* renderPass) const {
         ASSERT(!IsError());
-        // TODO(cwallez@chromium.org): This is called on every SetPipeline command. Optimize it for
-        // example by caching some "attachment compatibility" object that would make the
-        // compatibility check a single pointer comparison.
 
-        if (renderPass->colorAttachmentsSet != mColorAttachmentsSet) {
+        if (renderPass->attachmentState.Get() != mAttachmentState.Get()) {
             return DAWN_VALIDATION_ERROR(
-                "Pipeline doesn't have same color attachments set as renderPass");
-        }
-
-        for (uint32_t i : IterateBitSet(mColorAttachmentsSet)) {
-            if (renderPass->colorAttachments[i].view->GetFormat().format !=
-                mColorStates[i].format) {
-                return DAWN_VALIDATION_ERROR(
-                    "Pipeline color attachment format doesn't match renderPass");
-            }
-        }
-
-        if (renderPass->hasDepthStencilAttachment != mHasDepthStencilAttachment) {
-            return DAWN_VALIDATION_ERROR(
-                "Pipeline depth stencil attachment doesn't match renderPass");
-        }
-
-        if (mHasDepthStencilAttachment &&
-            (renderPass->depthStencilAttachment.view->GetFormat().format !=
-             mDepthStencilState.format)) {
-            return DAWN_VALIDATION_ERROR(
-                "Pipeline depth stencil attachment format doesn't match renderPass");
-        }
-
-        if (renderPass->sampleCount != mSampleCount) {
-            return DAWN_VALIDATION_ERROR("Pipeline sample count doesn't match renderPass");
+                "Pipeline attachment state is not compatible with render pass");
         }
 
         return {};
@@ -564,20 +535,23 @@
         HashCombine(&hash, pipeline->mVertexModule.Get(), pipeline->mFragmentEntryPoint);
         HashCombine(&hash, pipeline->mFragmentModule.Get(), pipeline->mFragmentEntryPoint);
 
+        // Hierarchically hash the attachment state.
+        // It contains the attachments set, texture formats, and sample count.
+        HashCombine(&hash, pipeline->mAttachmentState.Get());
+
         // Hash attachments
-        HashCombine(&hash, pipeline->mColorAttachmentsSet);
-        for (uint32_t i : IterateBitSet(pipeline->mColorAttachmentsSet)) {
+        for (uint32_t i : IterateBitSet(pipeline->mAttachmentState->GetColorAttachmentsMask())) {
             const ColorStateDescriptor& desc = *pipeline->GetColorStateDescriptor(i);
-            HashCombine(&hash, desc.format, desc.writeMask);
+            HashCombine(&hash, desc.writeMask);
             HashCombine(&hash, desc.colorBlend.operation, desc.colorBlend.srcFactor,
                         desc.colorBlend.dstFactor);
             HashCombine(&hash, desc.alphaBlend.operation, desc.alphaBlend.srcFactor,
                         desc.alphaBlend.dstFactor);
         }
 
-        if (pipeline->mHasDepthStencilAttachment) {
+        if (pipeline->mAttachmentState->HasDepthStencilAttachment()) {
             const DepthStencilStateDescriptor& desc = pipeline->mDepthStencilState;
-            HashCombine(&hash, desc.format, desc.depthWriteEnabled, desc.depthCompare);
+            HashCombine(&hash, desc.depthWriteEnabled, desc.depthCompare);
             HashCombine(&hash, desc.stencilReadMask, desc.stencilWriteMask);
             HashCombine(&hash, desc.stencilFront.compare, desc.stencilFront.failOp,
                         desc.stencilFront.depthFailOp, desc.stencilFront.passOp);
@@ -608,8 +582,8 @@
         }
 
         // Hash other state
-        HashCombine(&hash, pipeline->mSampleCount, pipeline->mPrimitiveTopology,
-                    pipeline->mSampleMask, pipeline->mAlphaToCoverageEnabled);
+        HashCombine(&hash, pipeline->mPrimitiveTopology, pipeline->mSampleMask,
+                    pipeline->mAlphaToCoverageEnabled);
 
         return hash;
     }
@@ -624,16 +598,16 @@
             return false;
         }
 
-        // Check attachments
-        if (a->mColorAttachmentsSet != b->mColorAttachmentsSet ||
-            a->mHasDepthStencilAttachment != b->mHasDepthStencilAttachment) {
+        // Check the attachment state.
+        // It contains the attachments set, texture formats, and sample count.
+        if (a->mAttachmentState.Get() != b->mAttachmentState.Get()) {
             return false;
         }
 
-        for (uint32_t i : IterateBitSet(a->mColorAttachmentsSet)) {
+        for (uint32_t i : IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) {
             const ColorStateDescriptor& descA = *a->GetColorStateDescriptor(i);
             const ColorStateDescriptor& descB = *b->GetColorStateDescriptor(i);
-            if (descA.format != descB.format || descA.writeMask != descB.writeMask) {
+            if (descA.writeMask != descB.writeMask) {
                 return false;
             }
             if (descA.colorBlend.operation != descB.colorBlend.operation ||
@@ -648,11 +622,10 @@
             }
         }
 
-        if (a->mHasDepthStencilAttachment) {
+        if (a->mAttachmentState->HasDepthStencilAttachment()) {
             const DepthStencilStateDescriptor& descA = a->mDepthStencilState;
             const DepthStencilStateDescriptor& descB = b->mDepthStencilState;
-            if (descA.format != descB.format ||
-                descA.depthWriteEnabled != descB.depthWriteEnabled ||
+            if (descA.depthWriteEnabled != descB.depthWriteEnabled ||
                 descA.depthCompare != descB.depthCompare) {
                 return false;
             }
@@ -720,8 +693,7 @@
         }
 
         // Check other state
-        if (a->mSampleCount != b->mSampleCount || a->mPrimitiveTopology != b->mPrimitiveTopology ||
-            a->mSampleMask != b->mSampleMask ||
+        if (a->mPrimitiveTopology != b->mPrimitiveTopology || a->mSampleMask != b->mSampleMask ||
             a->mAlphaToCoverageEnabled != b->mAlphaToCoverageEnabled) {
             return false;
         }
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index dfc46f7..f8e884e 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_RENDERPIPELINE_H_
 #define DAWNNATIVE_RENDERPIPELINE_H_
 
+#include "dawn_native/AttachmentState.h"
 #include "dawn_native/Pipeline.h"
 
 #include "dawn_native/dawn_platform.h"
@@ -102,15 +103,13 @@
         std::array<VertexBufferInfo, kMaxVertexBuffers> mInputInfos;
 
         // Attachments
-        bool mHasDepthStencilAttachment = false;
+        Ref<AttachmentState> mAttachmentState;
         DepthStencilStateDescriptor mDepthStencilState;
-        std::bitset<kMaxColorAttachments> mColorAttachmentsSet;
         std::array<ColorStateDescriptor, kMaxColorAttachments> mColorStates;
 
         // Other state
         dawn::PrimitiveTopology mPrimitiveTopology;
         RasterizationStateDescriptor mRasterizationState;
-        uint32_t mSampleCount;
         uint32_t mSampleMask;
         bool mAlphaToCoverageEnabled;
 
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 3e201e8..a70338c 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -249,8 +249,9 @@
         void TrackRenderPass(const BeginRenderPassCmd* renderPass) {
             DAWN_ASSERT(mRTVHeap.Get() == nullptr && mDSVHeap.Get() == nullptr);
 
-            mNumRTVs += static_cast<uint32_t>(renderPass->colorAttachmentsSet.count());
-            if (renderPass->hasDepthStencilAttachment) {
+            mNumRTVs += static_cast<uint32_t>(
+                renderPass->attachmentState->GetColorAttachmentsMask().count());
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 ++mNumDSVs;
             }
         }
@@ -273,9 +274,11 @@
             OMSetRenderTargetArgs args = {};
 
             unsigned int rtvIndex = 0;
-            uint32_t rtvCount = static_cast<uint32_t>(renderPass->colorAttachmentsSet.count());
+            uint32_t rtvCount = static_cast<uint32_t>(
+                renderPass->attachmentState->GetColorAttachmentsMask().count());
             DAWN_ASSERT(mAllocatedRTVs + rtvCount <= mNumRTVs);
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 TextureView* view = ToBackend(renderPass->colorAttachments[i].view).Get();
                 D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = mRTVHeap.GetCPUHandle(mAllocatedRTVs);
                 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = view->GetRTVDescriptor();
@@ -288,7 +291,7 @@
             }
             args.numRTVs = rtvIndex;
 
-            if (renderPass->hasDepthStencilAttachment) {
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 DAWN_ASSERT(mAllocatedDSVs < mNumDSVs);
                 TextureView* view = ToBackend(renderPass->depthStencilAttachment.view).Get();
                 D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = mDSVHeap.GetCPUHandle(mAllocatedDSVs);
@@ -372,7 +375,8 @@
                                            BeginRenderPassCmd* renderPass) {
             ASSERT(renderPass != nullptr);
 
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 TextureViewBase* resolveTarget =
                     renderPass->colorAttachments[i].resolveTarget.Get();
                 if (resolveTarget == nullptr) {
@@ -759,7 +763,8 @@
 
         // Clear framebuffer attachments as needed and transition to render target
         {
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 auto& attachmentInfo = renderPass->colorAttachments[i];
                 TextureView* view = ToBackend(attachmentInfo.view.Get());
 
@@ -785,7 +790,7 @@
                 }
             }
 
-            if (renderPass->hasDepthStencilAttachment) {
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 auto& attachmentInfo = renderPass->depthStencilAttachment;
                 Texture* texture = ToBackend(renderPass->depthStencilAttachment.view->GetTexture());
                 if ((texture->GetFormat().HasDepth() &&
@@ -864,7 +869,7 @@
 
                     // TODO(brandon1.jones@intel.com): avoid calling this function and enable MSAA
                     // resolve in D3D12 render pass on the platforms that support this feature.
-                    if (renderPass->sampleCount > 1) {
+                    if (renderPass->attachmentState->GetSampleCount() > 1) {
                         ResolveMultisampledRenderPass(commandList, renderPass);
                     }
                     return;
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index c6c440a..5f6331f 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -50,7 +50,8 @@
         MTLRenderPassDescriptor* CreateMTLRenderPassDescriptor(BeginRenderPassCmd* renderPass) {
             MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
 
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 auto& attachmentInfo = renderPass->colorAttachments[i];
 
                 if (attachmentInfo.loadOp == dawn::LoadOp::Clear) {
@@ -83,7 +84,7 @@
                 }
             }
 
-            if (renderPass->hasDepthStencilAttachment) {
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 auto& attachmentInfo = renderPass->depthStencilAttachment;
 
                 // TODO(jiawei.shao@intel.com): support rendering into a layer of a texture.
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 8e4a3ea..c520a3f 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -283,7 +283,8 @@
             GLuint readFbo = 0;
             GLuint writeFbo = 0;
 
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 if (renderPass->colorAttachments[i].resolveTarget.Get() != nullptr) {
                     if (readFbo == 0) {
                         ASSERT(writeFbo == 0);
@@ -590,7 +591,8 @@
             // Construct GL framebuffer
 
             unsigned int attachmentCount = 0;
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 TextureViewBase* textureView = renderPass->colorAttachments[i].view.Get();
                 GLuint texture = ToBackend(textureView->GetTexture())->GetHandle();
 
@@ -617,7 +619,7 @@
             }
             gl.DrawBuffers(attachmentCount, drawBuffers.data());
 
-            if (renderPass->hasDepthStencilAttachment) {
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 TextureViewBase* textureView = renderPass->depthStencilAttachment.view.Get();
                 GLuint texture = ToBackend(textureView->GetTexture())->GetHandle();
                 const Format& format = textureView->GetTexture()->GetFormat();
@@ -660,7 +662,8 @@
 
         // Clear framebuffer attachments as needed
         {
-            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+            for (uint32_t i :
+                 IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                 const auto& attachmentInfo = renderPass->colorAttachments[i];
 
                 // Load op - color
@@ -670,7 +673,7 @@
                 }
             }
 
-            if (renderPass->hasDepthStencilAttachment) {
+            if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                 const auto& attachmentInfo = renderPass->depthStencilAttachment;
                 const Format& attachmentFormat = attachmentInfo.view->GetTexture()->GetFormat();
 
@@ -710,7 +713,7 @@
                 case Command::EndRenderPass: {
                     mCommands.NextCommand<EndRenderPassCmd>();
 
-                    if (renderPass->sampleCount > 1) {
+                    if (renderPass->attachmentState->GetSampleCount() > 1) {
                         ResolveMultisampledRenderTargets(gl, renderPass);
                     }
                     gl.DeleteFramebuffers(1, &fbo);
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 8f1afa3..c682a6d 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -197,7 +197,8 @@
             {
                 RenderPassCacheQuery query;
 
-                for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+                for (uint32_t i :
+                     IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                     auto& attachmentInfo = renderPass->colorAttachments[i];
                     TextureView* view = ToBackend(attachmentInfo.view.Get());
                     bool hasResolveTarget = attachmentInfo.resolveTarget.Get() != nullptr;
@@ -223,7 +224,7 @@
                                    hasResolveTarget);
                 }
 
-                if (renderPass->hasDepthStencilAttachment) {
+                if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                     auto& attachmentInfo = renderPass->depthStencilAttachment;
                     query.SetDepthStencil(attachmentInfo.view->GetTexture()->GetFormat().format,
                                           attachmentInfo.depthLoadOp, attachmentInfo.stencilLoadOp);
@@ -238,7 +239,7 @@
                     }
                 }
 
-                query.SetSampleCount(renderPass->sampleCount);
+                query.SetSampleCount(renderPass->attachmentState->GetSampleCount());
 
                 renderPassVK = device->GetRenderPassCache()->GetRenderPass(query);
             }
@@ -252,7 +253,8 @@
                 // Fill in the attachment info that will be chained in the framebuffer create info.
                 std::array<VkImageView, kMaxColorAttachments * 2 + 1> attachments;
 
-                for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+                for (uint32_t i :
+                     IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                     auto& attachmentInfo = renderPass->colorAttachments[i];
                     TextureView* view = ToBackend(attachmentInfo.view.Get());
 
@@ -266,7 +268,7 @@
                     attachmentCount++;
                 }
 
-                if (renderPass->hasDepthStencilAttachment) {
+                if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                     auto& attachmentInfo = renderPass->depthStencilAttachment;
                     TextureView* view = ToBackend(attachmentInfo.view.Get());
 
@@ -278,7 +280,8 @@
                     attachmentCount++;
                 }
 
-                for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+                for (uint32_t i :
+                     IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
                     if (renderPass->colorAttachments[i].resolveTarget.Get() != nullptr) {
                         TextureView* view =
                             ToBackend(renderPass->colorAttachments[i].resolveTarget.Get());