Pipeline caching: refactor object hashing

Dawn's object-based cache creates keys from memory
addresses. These keys cannot be used in a persistent
cache. This change modifies the keys to only
use hashes so they can be re-used for caching
pipelines.

BUG=dawn:549

Change-Id: Ica64d58ae6a3c6266435cfc3f776c820190f7895
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/30740
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
diff --git a/src/dawn_native/AttachmentState.cpp b/src/dawn_native/AttachmentState.cpp
index 77b669c..a9c23ae 100644
--- a/src/dawn_native/AttachmentState.cpp
+++ b/src/dawn_native/AttachmentState.cpp
@@ -15,8 +15,8 @@
 #include "dawn_native/AttachmentState.h"
 
 #include "common/BitSetIterator.h"
-#include "common/HashUtils.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/Texture.h"
 
 namespace dawn_native {
@@ -130,6 +130,11 @@
         GetDevice()->UncacheAttachmentState(this);
     }
 
+    size_t AttachmentState::ComputeContentHash() {
+        // TODO(dawn:549): skip this traversal and reuse the blueprint.
+        return AttachmentStateBlueprint::HashFunc()(this);
+    }
+
     ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments>
     AttachmentState::GetColorAttachmentsMask() const {
         return mColorAttachmentsSet;
diff --git a/src/dawn_native/AttachmentState.h b/src/dawn_native/AttachmentState.h
index e332859..ce8b8aa 100644
--- a/src/dawn_native/AttachmentState.h
+++ b/src/dawn_native/AttachmentState.h
@@ -69,6 +69,8 @@
         wgpu::TextureFormat GetDepthStencilFormat() const;
         uint32_t GetSampleCount() const;
 
+        size_t ComputeContentHash() override;
+
       private:
         ~AttachmentState() override;
     };
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index 4d0bd76..a091a57 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -218,6 +218,8 @@
     "InternalPipelineStore.h",
     "ObjectBase.cpp",
     "ObjectBase.h",
+    "ObjectContentHasher.cpp",
+    "ObjectContentHasher.h",
     "PassResourceUsage.h",
     "PassResourceUsageTracker.cpp",
     "PassResourceUsageTracker.h",
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 9348607..3b0a994 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -15,8 +15,8 @@
 #include "dawn_native/BindGroupLayout.h"
 
 #include "common/BitSetIterator.h"
-#include "common/HashUtils.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/PerStage.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
@@ -152,11 +152,6 @@
 
     namespace {
 
-        void HashCombineBindingInfo(size_t* hash, const BindingInfo& info) {
-            HashCombine(hash, info.hasDynamicOffset, info.visibility, info.type,
-                        info.textureComponentType, info.viewDimension, info.storageTextureFormat,
-                        info.minBufferBindingSize);
-        }
 
         bool operator!=(const BindingInfo& a, const BindingInfo& b) {
             return a.hasDynamicOffset != b.hasDynamicOffset ||          //
@@ -319,15 +314,20 @@
         return it->second;
     }
 
-    size_t BindGroupLayoutBase::HashFunc::operator()(const BindGroupLayoutBase* bgl) const {
-        size_t hash = 0;
+    size_t BindGroupLayoutBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
         // std::map is sorted by key, so two BGLs constructed in different orders
-        // will still hash the same.
-        for (const auto& it : bgl->mBindingMap) {
-            HashCombine(&hash, it.first, it.second);
-            HashCombineBindingInfo(&hash, bgl->mBindingInfo[it.second]);
+        // will still record the same.
+        for (const auto& it : mBindingMap) {
+            recorder.Record(it.first, it.second);
+
+            const BindingInfo& info = mBindingInfo[it.second];
+            recorder.Record(info.hasDynamicOffset, info.visibility, info.type,
+                            info.textureComponentType, info.viewDimension,
+                            info.storageTextureFormat, info.minBufferBindingSize);
         }
-        return hash;
+
+        return recorder.GetContentHash();
     }
 
     bool BindGroupLayoutBase::EqualityFunc::operator()(const BindGroupLayoutBase* a,
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 06a7653..113c591 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -56,10 +56,9 @@
         const BindingMap& GetBindingMap() const;
         BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
 
-        // Functors necessary for the unordered_set<BGLBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const BindGroupLayoutBase* bgl) const;
-        };
+        // Functions necessary for the unordered_set<BGLBase*>-based cache.
+        size_t ComputeContentHash() override;
+
         struct EqualityFunc {
             bool operator()(const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const;
         };
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index e9b9580..a38446b 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -94,6 +94,8 @@
     "ErrorScopeTracker.h"
     "Extensions.cpp"
     "Extensions.h"
+    "ObjectContentHasher.cpp"
+    "ObjectContentHasher.h"
     "Fence.cpp"
     "Fence.h"
     "Format.cpp"
diff --git a/src/dawn_native/CachedObject.cpp b/src/dawn_native/CachedObject.cpp
index b91baed..523924d 100644
--- a/src/dawn_native/CachedObject.cpp
+++ b/src/dawn_native/CachedObject.cpp
@@ -14,6 +14,8 @@
 
 #include "dawn_native/CachedObject.h"
 
+#include "common/Assert.h"
+
 namespace dawn_native {
 
     bool CachedObject::IsCachedReference() const {
@@ -24,4 +26,19 @@
         mIsCachedReference = true;
     }
 
+    size_t CachedObject::HashFunc::operator()(const CachedObject* obj) const {
+        return obj->GetContentHash();
+    }
+
+    size_t CachedObject::GetContentHash() const {
+        ASSERT(mIsContentHashInitialized);
+        return mContentHash;
+    }
+
+    void CachedObject::SetContentHash(size_t contentHash) {
+        ASSERT(!mIsContentHashInitialized);
+        mContentHash = contentHash;
+        mIsContentHashInitialized = true;
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/CachedObject.h b/src/dawn_native/CachedObject.h
index b498b91..ec73718 100644
--- a/src/dawn_native/CachedObject.h
+++ b/src/dawn_native/CachedObject.h
@@ -29,11 +29,25 @@
 
         bool IsCachedReference() const;
 
+        // Functor necessary for the unordered_set<CachedObject*>-based cache.
+        struct HashFunc {
+            size_t operator()(const CachedObject* obj) const;
+        };
+
+        size_t GetContentHash() const;
+        void SetContentHash(size_t contentHash);
+
       private:
         friend class DeviceBase;
         void SetIsCachedReference();
 
         bool mIsCachedReference = false;
+
+        // Called by ObjectContentHasher upon creation to record the object.
+        virtual size_t ComputeContentHash() = 0;
+
+        size_t mContentHash = 0;
+        bool mIsContentHashInitialized = false;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/ComputePipeline.cpp b/src/dawn_native/ComputePipeline.cpp
index 793765d..e32e62a 100644
--- a/src/dawn_native/ComputePipeline.cpp
+++ b/src/dawn_native/ComputePipeline.cpp
@@ -14,8 +14,8 @@
 
 #include "dawn_native/ComputePipeline.h"
 
-#include "common/HashUtils.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 
 namespace dawn_native {
 
@@ -59,10 +59,6 @@
         return new ComputePipelineBase(device, ObjectBase::kError);
     }
 
-    size_t ComputePipelineBase::HashFunc::operator()(const ComputePipelineBase* pipeline) const {
-        return PipelineBase::HashForCache(pipeline);
-    }
-
     bool ComputePipelineBase::EqualityFunc::operator()(const ComputePipelineBase* a,
                                                        const ComputePipelineBase* b) const {
         return PipelineBase::EqualForCache(a, b);
diff --git a/src/dawn_native/ComputePipeline.h b/src/dawn_native/ComputePipeline.h
index c2b470a..012e5df 100644
--- a/src/dawn_native/ComputePipeline.h
+++ b/src/dawn_native/ComputePipeline.h
@@ -35,9 +35,6 @@
         const EntryPointMetadata& GetMetadata() const;
 
         // Functors necessary for the unordered_set<ComputePipelineBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const ComputePipelineBase* pipeline) const;
-        };
         struct EqualityFunc {
             bool operator()(const ComputePipelineBase* a, const ComputePipelineBase* b) const;
         };
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 2c8468f..3e838ad 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -437,6 +437,9 @@
         const BindGroupLayoutDescriptor* descriptor) {
         BindGroupLayoutBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         Ref<BindGroupLayoutBase> result = nullptr;
         auto iter = mCaches->bindGroupLayouts.find(&blueprint);
         if (iter != mCaches->bindGroupLayouts.end()) {
@@ -445,6 +448,7 @@
             BindGroupLayoutBase* backendObj;
             DAWN_TRY_ASSIGN(backendObj, CreateBindGroupLayoutImpl(descriptor));
             backendObj->SetIsCachedReference();
+            backendObj->SetContentHash(blueprintHash);
             mCaches->bindGroupLayouts.insert(backendObj);
             result = AcquireRef(backendObj);
         }
@@ -475,6 +479,9 @@
         const ComputePipelineDescriptor* descriptor) {
         ComputePipelineBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         auto iter = mCaches->computePipelines.find(&blueprint);
         if (iter != mCaches->computePipelines.end()) {
             (*iter)->Reference();
@@ -484,6 +491,7 @@
         ComputePipelineBase* backendObj;
         DAWN_TRY_ASSIGN(backendObj, CreateComputePipelineImpl(descriptor));
         backendObj->SetIsCachedReference();
+        backendObj->SetContentHash(blueprintHash);
         mCaches->computePipelines.insert(backendObj);
         return backendObj;
     }
@@ -498,6 +506,9 @@
         const PipelineLayoutDescriptor* descriptor) {
         PipelineLayoutBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         auto iter = mCaches->pipelineLayouts.find(&blueprint);
         if (iter != mCaches->pipelineLayouts.end()) {
             (*iter)->Reference();
@@ -507,6 +518,7 @@
         PipelineLayoutBase* backendObj;
         DAWN_TRY_ASSIGN(backendObj, CreatePipelineLayoutImpl(descriptor));
         backendObj->SetIsCachedReference();
+        backendObj->SetContentHash(blueprintHash);
         mCaches->pipelineLayouts.insert(backendObj);
         return backendObj;
     }
@@ -521,6 +533,9 @@
         const RenderPipelineDescriptor* descriptor) {
         RenderPipelineBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         auto iter = mCaches->renderPipelines.find(&blueprint);
         if (iter != mCaches->renderPipelines.end()) {
             (*iter)->Reference();
@@ -530,6 +545,7 @@
         RenderPipelineBase* backendObj;
         DAWN_TRY_ASSIGN(backendObj, CreateRenderPipelineImpl(descriptor));
         backendObj->SetIsCachedReference();
+        backendObj->SetContentHash(blueprintHash);
         mCaches->renderPipelines.insert(backendObj);
         return backendObj;
     }
@@ -544,6 +560,9 @@
         const SamplerDescriptor* descriptor) {
         SamplerBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         auto iter = mCaches->samplers.find(&blueprint);
         if (iter != mCaches->samplers.end()) {
             (*iter)->Reference();
@@ -553,6 +572,7 @@
         SamplerBase* backendObj;
         DAWN_TRY_ASSIGN(backendObj, CreateSamplerImpl(descriptor));
         backendObj->SetIsCachedReference();
+        backendObj->SetContentHash(blueprintHash);
         mCaches->samplers.insert(backendObj);
         return backendObj;
     }
@@ -567,6 +587,9 @@
         const ShaderModuleDescriptor* descriptor) {
         ShaderModuleBase blueprint(this, descriptor);
 
+        const size_t blueprintHash = blueprint.ComputeContentHash();
+        blueprint.SetContentHash(blueprintHash);
+
         auto iter = mCaches->shaderModules.find(&blueprint);
         if (iter != mCaches->shaderModules.end()) {
             (*iter)->Reference();
@@ -576,6 +599,7 @@
         ShaderModuleBase* backendObj;
         DAWN_TRY_ASSIGN(backendObj, CreateShaderModuleImpl(descriptor));
         backendObj->SetIsCachedReference();
+        backendObj->SetContentHash(blueprintHash);
         mCaches->shaderModules.insert(backendObj);
         return backendObj;
     }
@@ -595,6 +619,7 @@
 
         Ref<AttachmentState> attachmentState = AcquireRef(new AttachmentState(this, *blueprint));
         attachmentState->SetIsCachedReference();
+        attachmentState->SetContentHash(attachmentState->ComputeContentHash());
         mCaches->attachmentStates.insert(attachmentState.Get());
         return attachmentState;
     }
diff --git a/src/dawn_native/ObjectContentHasher.cpp b/src/dawn_native/ObjectContentHasher.cpp
new file mode 100644
index 0000000..901e4cc
--- /dev/null
+++ b/src/dawn_native/ObjectContentHasher.cpp
@@ -0,0 +1,22 @@
+// Copyright 2020 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/ObjectContentHasher.h"
+
+namespace dawn_native {
+
+    size_t ObjectContentHasher::GetContentHash() const {
+        return mContentHash;
+    }
+}  // namespace dawn_native
diff --git a/src/dawn_native/ObjectContentHasher.h b/src/dawn_native/ObjectContentHasher.h
new file mode 100644
index 0000000..2fed270
--- /dev/null
+++ b/src/dawn_native/ObjectContentHasher.h
@@ -0,0 +1,81 @@
+// Copyright 2020 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_OBJECT_CONTENT_HASHER_H_
+#define DAWNNATIVE_OBJECT_CONTENT_HASHER_H_
+
+#include "common/HashUtils.h"
+
+#include <string>
+#include <vector>
+
+namespace dawn_native {
+
+    // ObjectContentHasher records a hash that can be used as a key to lookup a cached object in a
+    // cache.
+    class ObjectContentHasher {
+      public:
+        // Record calls the appropriate record function based on the type.
+        template <typename T, typename... Args>
+        void Record(const T& value, const Args&... args) {
+            RecordImpl<T, Args...>::Call(this, value, args...);
+        }
+
+        size_t GetContentHash() const;
+
+      private:
+        template <typename T, typename... Args>
+        struct RecordImpl {
+            static constexpr void Call(ObjectContentHasher* recorder,
+                                       const T& value,
+                                       const Args&... args) {
+                HashCombine(&recorder->mContentHash, value, args...);
+            }
+        };
+
+        template <typename T>
+        struct RecordImpl<T*> {
+            static constexpr void Call(ObjectContentHasher* recorder, T* obj) {
+                // Calling Record(objPtr) is not allowed. This check exists to only prevent such
+                // mistakes.
+                static_assert(obj == nullptr, "");
+            }
+        };
+
+        template <>
+        struct RecordImpl<std::string> {
+            static constexpr void Call(ObjectContentHasher* recorder, const std::string& str) {
+                recorder->RecordIterable<std::string>(str);
+            }
+        };
+
+        template <typename T>
+        struct RecordImpl<std::vector<T>> {
+            static constexpr void Call(ObjectContentHasher* recorder, const std::vector<T>& vec) {
+                recorder->RecordIterable<std::vector<T>>(vec);
+            }
+        };
+
+        template <typename IteratorT>
+        void RecordIterable(const IteratorT& iterable) {
+            for (auto it = iterable.begin(); it != iterable.end(); ++it) {
+                Record(*it);
+            }
+        }
+
+        size_t mContentHash = 0;
+    };
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_OBJECT_CONTENT_HASHER_H_
\ No newline at end of file
diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp
index a006ddb..4d582e1 100644
--- a/src/dawn_native/Pipeline.cpp
+++ b/src/dawn_native/Pipeline.cpp
@@ -14,9 +14,9 @@
 
 #include "dawn_native/Pipeline.h"
 
-#include "common/HashUtils.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/ShaderModule.h"
 
@@ -142,21 +142,17 @@
         return bgl;
     }
 
-    // static
-    size_t PipelineBase::HashForCache(const PipelineBase* pipeline) {
-        size_t hash = 0;
+    size_t PipelineBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
+        recorder.Record(mLayout->GetContentHash());
 
-        // The layout is deduplicated so it can be hashed by pointer.
-        HashCombine(&hash, pipeline->mLayout.Get());
-
-        HashCombine(&hash, pipeline->mStageMask);
-        for (SingleShaderStage stage : IterateStages(pipeline->mStageMask)) {
-            // The module is deduplicated so it can be hashed by pointer.
-            HashCombine(&hash, pipeline->mStages[stage].module.Get());
-            HashCombine(&hash, pipeline->mStages[stage].entryPoint);
+        recorder.Record(mStageMask);
+        for (SingleShaderStage stage : IterateStages(mStageMask)) {
+            recorder.Record(mStages[stage].module->GetContentHash());
+            recorder.Record(mStages[stage].entryPoint);
         }
 
-        return hash;
+        return recorder.GetContentHash();
     }
 
     // static
diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h
index 395410a..cb18032 100644
--- a/src/dawn_native/Pipeline.h
+++ b/src/dawn_native/Pipeline.h
@@ -51,8 +51,8 @@
 
         BindGroupLayoutBase* GetBindGroupLayout(uint32_t groupIndex);
 
-        // Helper function for the functors for std::unordered_map-based pipeline caches.
-        static size_t HashForCache(const PipelineBase* pipeline);
+        // Helper functions for std::unordered_map-based pipeline caches.
+        size_t ComputeContentHash() override;
         static bool EqualForCache(const PipelineBase* a, const PipelineBase* b);
 
       protected:
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index dedef90..75839c7 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -16,10 +16,10 @@
 
 #include "common/Assert.h"
 #include "common/BitSetIterator.h"
-#include "common/HashUtils.h"
 #include "common/ityp_stack_vec.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/ShaderModule.h"
 
 namespace dawn_native {
@@ -253,14 +253,15 @@
         return kMaxBindGroupsTyped;
     }
 
-    size_t PipelineLayoutBase::HashFunc::operator()(const PipelineLayoutBase* pl) const {
-        size_t hash = Hash(pl->mMask);
+    size_t PipelineLayoutBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
+        recorder.Record(mMask);
 
-        for (BindGroupIndex group : IterateBitSet(pl->mMask)) {
-            HashCombine(&hash, pl->GetBindGroupLayout(group));
+        for (BindGroupIndex group : IterateBitSet(mMask)) {
+            recorder.Record(GetBindGroupLayout(group)->GetContentHash());
         }
 
-        return hash;
+        return recorder.GetContentHash();
     }
 
     bool PipelineLayoutBase::EqualityFunc::operator()(const PipelineLayoutBase* a,
diff --git a/src/dawn_native/PipelineLayout.h b/src/dawn_native/PipelineLayout.h
index be8a75c..9c30274 100644
--- a/src/dawn_native/PipelineLayout.h
+++ b/src/dawn_native/PipelineLayout.h
@@ -61,10 +61,9 @@
         // [0, kMaxBindGroups]
         BindGroupIndex GroupsInheritUpTo(const PipelineLayoutBase* other) const;
 
-        // Functors necessary for the unordered_set<PipelineLayoutBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const PipelineLayoutBase* pl) const;
-        };
+        // Functions necessary for the unordered_set<PipelineLayoutBase*>-based cache.
+        size_t ComputeContentHash() override;
+
         struct EqualityFunc {
             bool operator()(const PipelineLayoutBase* a, const PipelineLayoutBase* b) const;
         };
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index deb0fd3..fcdb75a 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -15,9 +15,9 @@
 #include "dawn_native/RenderPipeline.h"
 
 #include "common/BitSetIterator.h"
-#include "common/HashUtils.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
 #include <cmath>
@@ -607,63 +607,62 @@
         return mAttachmentState.Get();
     }
 
-    size_t RenderPipelineBase::HashFunc::operator()(const RenderPipelineBase* pipeline) const {
-        // Hash modules and layout
-        size_t hash = PipelineBase::HashForCache(pipeline);
+    size_t RenderPipelineBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
 
-        // Hierarchically hash the attachment state.
+        // Record modules and layout
+        recorder.Record(PipelineBase::ComputeContentHash());
+
+        // Hierarchically record the attachment state.
         // It contains the attachments set, texture formats, and sample count.
-        HashCombine(&hash, pipeline->mAttachmentState.Get());
+        recorder.Record(mAttachmentState->GetContentHash());
 
-        // Hash attachments
-        for (ColorAttachmentIndex i :
-             IterateBitSet(pipeline->mAttachmentState->GetColorAttachmentsMask())) {
-            const ColorStateDescriptor& desc = *pipeline->GetColorStateDescriptor(i);
-            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);
+        // Record attachments
+        for (ColorAttachmentIndex i : IterateBitSet(mAttachmentState->GetColorAttachmentsMask())) {
+            const ColorStateDescriptor& desc = *GetColorStateDescriptor(i);
+            recorder.Record(desc.writeMask);
+            recorder.Record(desc.colorBlend.operation, desc.colorBlend.srcFactor,
+                            desc.colorBlend.dstFactor);
+            recorder.Record(desc.alphaBlend.operation, desc.alphaBlend.srcFactor,
+                            desc.alphaBlend.dstFactor);
         }
 
-        if (pipeline->mAttachmentState->HasDepthStencilAttachment()) {
-            const DepthStencilStateDescriptor& desc = pipeline->mDepthStencilState;
-            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);
-            HashCombine(&hash, desc.stencilBack.compare, desc.stencilBack.failOp,
-                        desc.stencilBack.depthFailOp, desc.stencilBack.passOp);
+        if (mAttachmentState->HasDepthStencilAttachment()) {
+            const DepthStencilStateDescriptor& desc = mDepthStencilState;
+            recorder.Record(desc.depthWriteEnabled, desc.depthCompare);
+            recorder.Record(desc.stencilReadMask, desc.stencilWriteMask);
+            recorder.Record(desc.stencilFront.compare, desc.stencilFront.failOp,
+                            desc.stencilFront.depthFailOp, desc.stencilFront.passOp);
+            recorder.Record(desc.stencilBack.compare, desc.stencilBack.failOp,
+                            desc.stencilBack.depthFailOp, desc.stencilBack.passOp);
         }
 
-        // Hash vertex state
-        HashCombine(&hash, pipeline->mAttributeLocationsUsed);
-        for (VertexAttributeLocation location : IterateBitSet(pipeline->mAttributeLocationsUsed)) {
-            const VertexAttributeInfo& desc = pipeline->GetAttribute(location);
-            HashCombine(&hash, desc.shaderLocation, desc.vertexBufferSlot, desc.offset,
-                        desc.format);
+        // Record vertex state
+        recorder.Record(mAttributeLocationsUsed);
+        for (VertexAttributeLocation location : IterateBitSet(mAttributeLocationsUsed)) {
+            const VertexAttributeInfo& desc = GetAttribute(location);
+            recorder.Record(desc.shaderLocation, desc.vertexBufferSlot, desc.offset, desc.format);
         }
 
-        HashCombine(&hash, pipeline->mVertexBufferSlotsUsed);
-        for (VertexBufferSlot slot : IterateBitSet(pipeline->mVertexBufferSlotsUsed)) {
-            const VertexBufferInfo& desc = pipeline->GetVertexBuffer(slot);
-            HashCombine(&hash, desc.arrayStride, desc.stepMode);
+        recorder.Record(mVertexBufferSlotsUsed);
+        for (VertexBufferSlot slot : IterateBitSet(mVertexBufferSlotsUsed)) {
+            const VertexBufferInfo& desc = GetVertexBuffer(slot);
+            recorder.Record(desc.arrayStride, desc.stepMode);
         }
 
-        HashCombine(&hash, pipeline->mVertexState.indexFormat);
+        recorder.Record(mVertexState.indexFormat);
 
-        // Hash rasterization state
+        // Record rasterization state
         {
-            const RasterizationStateDescriptor& desc = pipeline->mRasterizationState;
-            HashCombine(&hash, desc.frontFace, desc.cullMode);
-            HashCombine(&hash, desc.depthBias, desc.depthBiasSlopeScale, desc.depthBiasClamp);
+            const RasterizationStateDescriptor& desc = mRasterizationState;
+            recorder.Record(desc.frontFace, desc.cullMode);
+            recorder.Record(desc.depthBias, desc.depthBiasSlopeScale, desc.depthBiasClamp);
         }
 
-        // Hash other state
-        HashCombine(&hash, pipeline->mPrimitiveTopology, pipeline->mSampleMask,
-                    pipeline->mAlphaToCoverageEnabled);
+        // Record other state
+        recorder.Record(mPrimitiveTopology, mSampleMask, mAlphaToCoverageEnabled);
 
-        return hash;
+        return recorder.GetContentHash();
     }
 
     bool RenderPipelineBase::EqualityFunc::operator()(const RenderPipelineBase* a,
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index db913a2..b6f047b 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -91,10 +91,9 @@
 
         const AttachmentState* GetAttachmentState() const;
 
-        // Functors necessary for the unordered_set<RenderPipelineBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const RenderPipelineBase* pipeline) const;
-        };
+        // Functions necessary for the unordered_set<RenderPipelineBase*>-based cache.
+        size_t ComputeContentHash() override;
+
         struct EqualityFunc {
             bool operator()(const RenderPipelineBase* a, const RenderPipelineBase* b) const;
         };
diff --git a/src/dawn_native/Sampler.cpp b/src/dawn_native/Sampler.cpp
index 748956e..11b398a 100644
--- a/src/dawn_native/Sampler.cpp
+++ b/src/dawn_native/Sampler.cpp
@@ -14,8 +14,8 @@
 
 #include "dawn_native/Sampler.h"
 
-#include "common/HashUtils.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
 #include <cmath>
@@ -84,20 +84,11 @@
         return mCompareFunction != wgpu::CompareFunction::Undefined;
     }
 
-    size_t SamplerBase::HashFunc::operator()(const SamplerBase* module) const {
-        size_t hash = 0;
-
-        HashCombine(&hash, module->mAddressModeU);
-        HashCombine(&hash, module->mAddressModeV);
-        HashCombine(&hash, module->mAddressModeW);
-        HashCombine(&hash, module->mMagFilter);
-        HashCombine(&hash, module->mMinFilter);
-        HashCombine(&hash, module->mMipmapFilter);
-        HashCombine(&hash, module->mLodMinClamp);
-        HashCombine(&hash, module->mLodMaxClamp);
-        HashCombine(&hash, module->mCompareFunction);
-
-        return hash;
+    size_t SamplerBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
+        recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter,
+                        mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction);
+        return recorder.GetContentHash();
     }
 
     bool SamplerBase::EqualityFunc::operator()(const SamplerBase* a, const SamplerBase* b) const {
diff --git a/src/dawn_native/Sampler.h b/src/dawn_native/Sampler.h
index 3829b90..62ef4d5 100644
--- a/src/dawn_native/Sampler.h
+++ b/src/dawn_native/Sampler.h
@@ -35,10 +35,9 @@
 
         bool HasCompareFunction() const;
 
-        // Functors necessary for the unordered_set<SamplerBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const SamplerBase* module) const;
-        };
+        // Functions necessary for the unordered_set<SamplerBase*>-based cache.
+        size_t ComputeContentHash() override;
+
         struct EqualityFunc {
             bool operator()(const SamplerBase* a, const SamplerBase* b) const;
         };
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index cba7da7..e7066e2 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -14,9 +14,9 @@
 
 #include "dawn_native/ShaderModule.h"
 
-#include "common/HashUtils.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ObjectContentHasher.h"
 #include "dawn_native/Pipeline.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/SpirvUtils.h"
@@ -975,20 +975,12 @@
         return *mEntryPoints.at(entryPoint);
     }
 
-    size_t ShaderModuleBase::HashFunc::operator()(const ShaderModuleBase* module) const {
-        size_t hash = 0;
-
-        HashCombine(&hash, module->mType);
-
-        for (uint32_t word : module->mOriginalSpirv) {
-            HashCombine(&hash, word);
-        }
-
-        for (char c : module->mWgsl) {
-            HashCombine(&hash, c);
-        }
-
-        return hash;
+    size_t ShaderModuleBase::ComputeContentHash() {
+        ObjectContentHasher recorder;
+        recorder.Record(mType);
+        recorder.Record(mOriginalSpirv);
+        recorder.Record(mWgsl);
+        return recorder.GetContentHash();
     }
 
     bool ShaderModuleBase::EqualityFunc::operator()(const ShaderModuleBase* a,
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index 3898515..e406f49 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -105,10 +105,9 @@
         // must be true.
         const EntryPointMetadata& GetEntryPoint(const std::string& entryPoint) const;
 
-        // Functors necessary for the unordered_set<ShaderModuleBase*>-based cache.
-        struct HashFunc {
-            size_t operator()(const ShaderModuleBase* module) const;
-        };
+        // Functions necessary for the unordered_set<ShaderModuleBase*>-based cache.
+        size_t ComputeContentHash() override;
+
         struct EqualityFunc {
             bool operator()(const ShaderModuleBase* a, const ShaderModuleBase* b) const;
         };