BGL refactoring to allow for auto pipelines to use cache better.

- Introduces BindGroupLayoutInternalBase that is equivalent to the
  old BindGroupLayoutBase.
- New BindGroupLayoutBase is now just a wrapper over the internal
  version with a tagged pipeline compatibility token.
- Adjusts the existing tests to accomodate the change.
- Follow up CL to separate the new class.

Bug: dawn:1933
Change-Id: Ia167b987b4dfa504bbb70c85f6a72c1b16725b85
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/143521
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h
index e4913a1..51b3cc6 100644
--- a/include/dawn/native/DawnNative.h
+++ b/include/dawn/native/DawnNative.h
@@ -294,9 +294,6 @@
 
 DAWN_NATIVE_EXPORT uint64_t GetAllocatedSizeForTesting(WGPUBuffer buffer);
 
-DAWN_NATIVE_EXPORT bool BindGroupLayoutBindingsEqualForTesting(WGPUBindGroupLayout a,
-                                                               WGPUBindGroupLayout b);
-
 }  // namespace dawn::native
 
 // Alias the DawnInstanceDescriptor up to wgpu.
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index f49f8ab..1e91b14 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -286,7 +286,7 @@
         descriptor->entryCount, static_cast<uint32_t>(descriptor->layout->GetBindingCount()),
         descriptor->layout, descriptor->layout->EntriesToString());
 
-    const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
+    const BindGroupLayoutInternalBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
     ASSERT(bindingMap.size() <= kMaxBindingsPerPipelineLayout);
 
     ityp::bitset<BindingIndex, kMaxBindingsPerPipelineLayout> bindingsSet;
diff --git a/src/dawn/native/BindGroup.h b/src/dawn/native/BindGroup.h
index 922d813..acf4363 100644
--- a/src/dawn/native/BindGroup.h
+++ b/src/dawn/native/BindGroup.h
@@ -87,7 +87,7 @@
     void DeleteThis() override;
 
     Ref<BindGroupLayoutBase> mLayout;
-    BindGroupLayoutBase::BindingDataPointers mBindingData;
+    BindGroupLayoutInternalBase::BindingDataPointers mBindingData;
 
     // TODO(dawn:1293): Store external textures in
     // BindGroupLayoutBase::BindingDataPointers::bindings
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp
index 6bb8c5f..d7dd201 100644
--- a/src/dawn/native/BindGroupLayout.cpp
+++ b/src/dawn/native/BindGroupLayout.cpp
@@ -458,15 +458,13 @@
 
 }  // namespace
 
-// BindGroupLayoutBase
+// BindGroupLayoutInternalBase
 
-BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
-                                         const BindGroupLayoutDescriptor* descriptor,
-                                         PipelineCompatibilityToken pipelineCompatibilityToken,
-                                         ApiObjectBase::UntrackedByDeviceTag tag)
-    : ApiObjectBase(device, descriptor->label),
-      mPipelineCompatibilityToken(pipelineCompatibilityToken),
-      mUnexpandedBindingCount(descriptor->entryCount) {
+BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
+    DeviceBase* device,
+    const BindGroupLayoutDescriptor* descriptor,
+    ApiObjectBase::UntrackedByDeviceTag tag)
+    : ApiObjectBase(device, descriptor->label), mUnexpandedBindingCount(descriptor->entryCount) {
     std::vector<BindGroupLayoutEntry> sortedBindings = ExtractAndExpandBglEntries(
         descriptor, &mBindingCounts, &mExternalTextureBindingExpansionMap);
 
@@ -490,52 +488,52 @@
     ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped);
 }
 
-BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
-                                         const BindGroupLayoutDescriptor* descriptor,
-                                         PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken, kUntrackedByDevice) {
+BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
+    DeviceBase* device,
+    const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor, kUntrackedByDevice) {
     GetObjectTrackingList()->Track(this);
 }
 
-BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
-                                         ObjectBase::ErrorTag tag,
-                                         const char* label)
+BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(DeviceBase* device,
+                                                         ObjectBase::ErrorTag tag,
+                                                         const char* label)
     : ApiObjectBase(device, tag, label) {}
 
-BindGroupLayoutBase::~BindGroupLayoutBase() = default;
+BindGroupLayoutInternalBase::~BindGroupLayoutInternalBase() = default;
 
-void BindGroupLayoutBase::DestroyImpl() {
+void BindGroupLayoutInternalBase::DestroyImpl() {
     Uncache();
 }
 
-// static
-BindGroupLayoutBase* BindGroupLayoutBase::MakeError(DeviceBase* device, const char* label) {
-    return new BindGroupLayoutBase(device, ObjectBase::kError, label);
-}
-
-ObjectType BindGroupLayoutBase::GetType() const {
+ObjectType BindGroupLayoutInternalBase::GetType() const {
     return ObjectType::BindGroupLayout;
 }
 
-const BindGroupLayoutBase::BindingMap& BindGroupLayoutBase::GetBindingMap() const {
+const BindingInfo& BindGroupLayoutInternalBase::GetBindingInfo(BindingIndex bindingIndex) const {
+    ASSERT(!IsError());
+    ASSERT(bindingIndex < mBindingInfo.size());
+    return mBindingInfo[bindingIndex];
+}
+
+const BindGroupLayoutInternalBase::BindingMap& BindGroupLayoutInternalBase::GetBindingMap() const {
     ASSERT(!IsError());
     return mBindingMap;
 }
 
-bool BindGroupLayoutBase::HasBinding(BindingNumber bindingNumber) const {
+bool BindGroupLayoutInternalBase::HasBinding(BindingNumber bindingNumber) const {
     return mBindingMap.count(bindingNumber) != 0;
 }
 
-BindingIndex BindGroupLayoutBase::GetBindingIndex(BindingNumber bindingNumber) const {
+BindingIndex BindGroupLayoutInternalBase::GetBindingIndex(BindingNumber bindingNumber) const {
     ASSERT(!IsError());
     const auto& it = mBindingMap.find(bindingNumber);
     ASSERT(it != mBindingMap.end());
     return it->second;
 }
 
-size_t BindGroupLayoutBase::ComputeContentHash() {
+size_t BindGroupLayoutInternalBase::ComputeContentHash() {
     ObjectContentHasher recorder;
-    recorder.Record(mPipelineCompatibilityToken);
 
     // std::map is sorted by key, so two BGLs constructed in different orders
     // will still record the same.
@@ -553,53 +551,49 @@
     return recorder.GetContentHash();
 }
 
-bool BindGroupLayoutBase::EqualityFunc::operator()(const BindGroupLayoutBase* a,
-                                                   const BindGroupLayoutBase* b) const {
+bool BindGroupLayoutInternalBase::EqualityFunc::operator()(
+    const BindGroupLayoutInternalBase* a,
+    const BindGroupLayoutInternalBase* b) const {
     return a->IsLayoutEqual(b);
 }
 
-BindingIndex BindGroupLayoutBase::GetBindingCount() const {
+BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const {
     return mBindingInfo.size();
 }
 
-BindingIndex BindGroupLayoutBase::GetBufferCount() const {
+BindingIndex BindGroupLayoutInternalBase::GetBufferCount() const {
     return BindingIndex(mBindingCounts.bufferCount);
 }
 
-BindingIndex BindGroupLayoutBase::GetDynamicBufferCount() const {
+BindingIndex BindGroupLayoutInternalBase::GetDynamicBufferCount() const {
     // This is a binding index because dynamic buffers are packed at the front of the binding
     // info.
     return static_cast<BindingIndex>(mBindingCounts.dynamicStorageBufferCount +
                                      mBindingCounts.dynamicUniformBufferCount);
 }
 
-uint32_t BindGroupLayoutBase::GetUnverifiedBufferCount() const {
+uint32_t BindGroupLayoutInternalBase::GetUnverifiedBufferCount() const {
     return mBindingCounts.unverifiedBufferCount;
 }
 
-uint32_t BindGroupLayoutBase::GetExternalTextureBindingCount() const {
+uint32_t BindGroupLayoutInternalBase::GetExternalTextureBindingCount() const {
     return mExternalTextureBindingExpansionMap.size();
 }
 
-const BindingCounts& BindGroupLayoutBase::GetBindingCountInfo() const {
+const BindingCounts& BindGroupLayoutInternalBase::GetBindingCountInfo() const {
     return mBindingCounts;
 }
 
 const ExternalTextureBindingExpansionMap&
-BindGroupLayoutBase::GetExternalTextureBindingExpansionMap() const {
+BindGroupLayoutInternalBase::GetExternalTextureBindingExpansionMap() const {
     return mExternalTextureBindingExpansionMap;
 }
 
-uint32_t BindGroupLayoutBase::GetUnexpandedBindingCount() const {
+uint32_t BindGroupLayoutInternalBase::GetUnexpandedBindingCount() const {
     return mUnexpandedBindingCount;
 }
 
-bool BindGroupLayoutBase::IsLayoutEqual(const BindGroupLayoutBase* other,
-                                        bool excludePipelineCompatibiltyToken) const {
-    if (!excludePipelineCompatibiltyToken &&
-        GetPipelineCompatibilityToken() != other->GetPipelineCompatibilityToken()) {
-        return false;
-    }
+bool BindGroupLayoutInternalBase::IsLayoutEqual(const BindGroupLayoutInternalBase* other) const {
     if (GetBindingCount() != other->GetBindingCount()) {
         return false;
     }
@@ -611,11 +605,7 @@
     return mBindingMap == other->mBindingMap;
 }
 
-PipelineCompatibilityToken BindGroupLayoutBase::GetPipelineCompatibilityToken() const {
-    return mPipelineCompatibilityToken;
-}
-
-size_t BindGroupLayoutBase::GetBindingDataSize() const {
+size_t BindGroupLayoutInternalBase::GetBindingDataSize() const {
     // | ------ buffer-specific ----------| ------------ object pointers -------------|
     // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------|
     // Followed by:
@@ -629,8 +619,8 @@
     return bufferSizeArrayStart + mBindingCounts.unverifiedBufferCount * sizeof(uint64_t);
 }
 
-BindGroupLayoutBase::BindingDataPointers BindGroupLayoutBase::ComputeBindingDataPointers(
-    void* dataStart) const {
+BindGroupLayoutInternalBase::BindingDataPointers
+BindGroupLayoutInternalBase::ComputeBindingDataPointers(void* dataStart) const {
     BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart);
     auto bindings = reinterpret_cast<Ref<ObjectBase>*>(bufferData + mBindingCounts.bufferCount);
     uint64_t* unverifiedBufferSizes = AlignPtr(
@@ -645,7 +635,7 @@
             {unverifiedBufferSizes, mBindingCounts.unverifiedBufferCount}};
 }
 
-bool BindGroupLayoutBase::IsStorageBufferBinding(BindingIndex bindingIndex) const {
+bool BindGroupLayoutInternalBase::IsStorageBufferBinding(BindingIndex bindingIndex) const {
     ASSERT(bindingIndex < GetBufferCount());
     switch (GetBindingInfo(bindingIndex).buffer.type) {
         case wgpu::BufferBindingType::Uniform:
@@ -660,10 +650,10 @@
     UNREACHABLE();
 }
 
-std::string BindGroupLayoutBase::EntriesToString() const {
+std::string BindGroupLayoutInternalBase::EntriesToString() const {
     std::string entries = "[";
     std::string sep = "";
-    const BindGroupLayoutBase::BindingMap& bindingMap = GetBindingMap();
+    const BindGroupLayoutInternalBase::BindingMap& bindingMap = GetBindingMap();
     for (const auto [bindingNumber, bindingIndex] : bindingMap) {
         const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
         entries += absl::StrFormat("%s%s", sep, bindingInfo);
@@ -673,4 +663,43 @@
     return entries;
 }
 
+BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
+                                         const char* label,
+                                         Ref<BindGroupLayoutInternalBase> internal,
+                                         PipelineCompatibilityToken pipelineCompatibilityToken)
+    : ApiObjectBase(device, label),
+      mInternalLayout(internal),
+      mPipelineCompatibilityToken(pipelineCompatibilityToken) {
+    GetObjectTrackingList()->Track(this);
+}
+
+BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
+                                         ObjectBase::ErrorTag tag,
+                                         const char* label)
+    : ApiObjectBase(device, tag, label) {}
+
+ObjectType BindGroupLayoutBase::GetType() const {
+    return ObjectType::BindGroupLayout;
+}
+
+// static
+BindGroupLayoutBase* BindGroupLayoutBase::MakeError(DeviceBase* device, const char* label) {
+    return new BindGroupLayoutBase(device, ObjectBase::kError, label);
+}
+
+BindGroupLayoutInternalBase* BindGroupLayoutBase::GetInternalBindGroupLayout() const {
+    return mInternalLayout.Get();
+}
+
+bool BindGroupLayoutBase::IsLayoutEqual(const BindGroupLayoutBase* other,
+                                        bool excludePipelineCompatibiltyToken) const {
+    if (!excludePipelineCompatibiltyToken &&
+        GetPipelineCompatibilityToken() != other->GetPipelineCompatibilityToken()) {
+        return false;
+    }
+    return GetInternalBindGroupLayout() == other->GetInternalBindGroupLayout();
+}
+
+void BindGroupLayoutBase::DestroyImpl() {}
+
 }  // namespace dawn::native
diff --git a/src/dawn/native/BindGroupLayout.h b/src/dawn/native/BindGroupLayout.h
index 77042b9..6bf49bb 100644
--- a/src/dawn/native/BindGroupLayout.h
+++ b/src/dawn/native/BindGroupLayout.h
@@ -50,31 +50,22 @@
 // Bindings are specified as a |BindingNumber| in the BindGroupLayoutDescriptor.
 // These numbers may be arbitrary and sparse. Internally, Dawn packs these numbers
 // into a packed range of |BindingIndex| integers.
-class BindGroupLayoutBase : public ApiObjectBase,
-                            public CachedObject,
-                            public ContentLessObjectCacheable<BindGroupLayoutBase> {
+class BindGroupLayoutInternalBase : public ApiObjectBase,
+                                    public CachedObject,
+                                    public ContentLessObjectCacheable<BindGroupLayoutInternalBase> {
   public:
-    BindGroupLayoutBase(DeviceBase* device,
-                        const BindGroupLayoutDescriptor* descriptor,
-                        PipelineCompatibilityToken pipelineCompatibilityToken,
-                        ApiObjectBase::UntrackedByDeviceTag tag);
-    BindGroupLayoutBase(DeviceBase* device,
-                        const BindGroupLayoutDescriptor* descriptor,
-                        PipelineCompatibilityToken pipelineCompatibilityToken);
-    ~BindGroupLayoutBase() override;
-
-    static BindGroupLayoutBase* MakeError(DeviceBase* device, const char* label = nullptr);
+    BindGroupLayoutInternalBase(DeviceBase* device,
+                                const BindGroupLayoutDescriptor* descriptor,
+                                ApiObjectBase::UntrackedByDeviceTag tag);
+    BindGroupLayoutInternalBase(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
+    ~BindGroupLayoutInternalBase() override;
 
     ObjectType GetType() const override;
 
     // A map from the BindingNumber to its packed BindingIndex.
     using BindingMap = std::map<BindingNumber, BindingIndex>;
 
-    const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const {
-        ASSERT(!IsError());
-        ASSERT(bindingIndex < mBindingInfo.size());
-        return mBindingInfo[bindingIndex];
-    }
+    const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const;
     const BindingMap& GetBindingMap() const;
     bool HasBinding(BindingNumber bindingNumber) const;
     BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
@@ -83,7 +74,8 @@
     size_t ComputeContentHash() override;
 
     struct EqualityFunc {
-        bool operator()(const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const;
+        bool operator()(const BindGroupLayoutInternalBase* a,
+                        const BindGroupLayoutInternalBase* b) const;
     };
 
     BindingIndex GetBindingCount() const;
@@ -104,11 +96,8 @@
 
     uint32_t GetUnexpandedBindingCount() const;
 
-    // Tests that the BindingInfo of two bind groups are equal,
-    // ignoring their compatibility groups.
-    bool IsLayoutEqual(const BindGroupLayoutBase* other,
-                       bool excludePipelineCompatibiltyToken = false) const;
-    PipelineCompatibilityToken GetPipelineCompatibilityToken() const;
+    // Tests that the BindingInfo of two bind groups are equal.
+    bool IsLayoutEqual(const BindGroupLayoutInternalBase* other) const;
 
     struct BufferBindingData {
         uint64_t offset;
@@ -149,7 +138,7 @@
     }
 
   private:
-    BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
+    BindGroupLayoutInternalBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
 
     BindingCounts mBindingCounts = {};
     ityp::vector<BindingIndex, BindingInfo> mBindingInfo;
@@ -159,10 +148,85 @@
 
     ExternalTextureBindingExpansionMap mExternalTextureBindingExpansionMap;
 
+    uint32_t mUnexpandedBindingCount;
+};
+
+// Wrapper passthrough frontend object that is essentially just a Ref to a backing
+// BindGroupLayoutInternalBase and a pipeline compatibility token.
+// TODO(lokokung) Could maybe make this a final class if we update the mock objects.
+class BindGroupLayoutBase : public ApiObjectBase {
+  public:
+    BindGroupLayoutBase(DeviceBase* device,
+                        const char* label,
+                        Ref<BindGroupLayoutInternalBase> internal,
+                        PipelineCompatibilityToken pipelineCompatibilityToken);
+
+    static BindGroupLayoutBase* MakeError(DeviceBase* device, const char* label = nullptr);
+
+    ObjectType GetType() const override;
+
+    // Proxy functions that just call their respective counterparts in the internal layout.
+    const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const {
+        return mInternalLayout->GetBindingInfo(bindingIndex);
+    }
+    const BindGroupLayoutInternalBase::BindingMap& GetBindingMap() const {
+        return mInternalLayout->GetBindingMap();
+    }
+    bool HasBinding(BindingNumber bindingNumber) const {
+        return mInternalLayout->HasBinding(bindingNumber);
+    }
+    BindingIndex GetBindingIndex(BindingNumber bindingNumber) const {
+        return mInternalLayout->GetBindingIndex(bindingNumber);
+    }
+    BindingIndex GetBindingCount() const { return mInternalLayout->GetBindingCount(); }
+    BindingIndex GetBufferCount() const { return mInternalLayout->GetBufferCount(); }
+    BindingIndex GetDynamicBufferCount() const { return mInternalLayout->GetDynamicBufferCount(); }
+    uint32_t GetUnverifiedBufferCount() const {
+        return mInternalLayout->GetUnverifiedBufferCount();
+    }
+    const BindingCounts& GetBindingCountInfo() const {
+        return mInternalLayout->GetBindingCountInfo();
+    }
+    uint32_t GetExternalTextureBindingCount() const {
+        return mInternalLayout->GetExternalTextureBindingCount();
+    }
+    const ExternalTextureBindingExpansionMap& GetExternalTextureBindingExpansionMap() const {
+        return mInternalLayout->GetExternalTextureBindingExpansionMap();
+    }
+    uint32_t GetUnexpandedBindingCount() const {
+        return mInternalLayout->GetUnexpandedBindingCount();
+    }
+    size_t GetBindingDataSize() const { return mInternalLayout->GetBindingDataSize(); }
+    static constexpr size_t GetBindingDataAlignment() {
+        return BindGroupLayoutInternalBase::GetBindingDataAlignment();
+    }
+    BindGroupLayoutInternalBase::BindingDataPointers ComputeBindingDataPointers(
+        void* dataStart) const {
+        return mInternalLayout->ComputeBindingDataPointers(dataStart);
+    }
+    bool IsStorageBufferBinding(BindingIndex bindingIndex) const {
+        return mInternalLayout->IsStorageBufferBinding(bindingIndex);
+    }
+    std::string EntriesToString() const { return mInternalLayout->EntriesToString(); }
+
+    // Non-proxy functions that are specific to the realized frontend object.
+    BindGroupLayoutInternalBase* GetInternalBindGroupLayout() const;
+    bool IsLayoutEqual(const BindGroupLayoutBase* other,
+                       bool excludePipelineCompatibiltyToken = false) const;
+    PipelineCompatibilityToken GetPipelineCompatibilityToken() const {
+        return mPipelineCompatibilityToken;
+    }
+
+  protected:
+    void DestroyImpl() override;
+
+  private:
+    BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
+
+    const Ref<BindGroupLayoutInternalBase> mInternalLayout;
+
     // Non-0 if this BindGroupLayout was created as part of a default PipelineLayout.
     const PipelineCompatibilityToken mPipelineCompatibilityToken = PipelineCompatibilityToken(0);
-
-    uint32_t mUnexpandedBindingCount;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp
index eae1916..6eb25bd 100644
--- a/src/dawn/native/CommandBufferStateTracker.cpp
+++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -510,7 +510,8 @@
 
         for (BindGroupIndex i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) {
             if (mBindgroups[i] == nullptr ||
-                mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout() ||
+                mLastPipelineLayout->GetBindGroupLayout(i)->GetInternalBindGroupLayout() !=
+                    mBindgroups[i]->GetLayout()->GetInternalBindGroupLayout() ||
                 FindFirstUndersizedBuffer(mBindgroups[i]->GetUnverifiedBufferSizes(),
                                           (*mMinBufferSizes)[i])
                     .has_value()) {
@@ -632,7 +633,8 @@
                 mBindgroups[i], static_cast<uint32_t>(i), currentBGL, mLastPipeline);
 
             DAWN_INVALID_IF(
-                mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout(),
+                mLastPipelineLayout->GetBindGroupLayout(i)->GetInternalBindGroupLayout() !=
+                    mBindgroups[i]->GetLayout()->GetInternalBindGroupLayout(),
                 "Bind group layout %s of pipeline layout %s does not match layout %s of bind "
                 "group %s set at group index %u.",
                 requiredBGL, mLastPipelineLayout, currentBGL, mBindgroups[i],
diff --git a/src/dawn/native/DawnNative.cpp b/src/dawn/native/DawnNative.cpp
index 7a1f008..f2fb809 100644
--- a/src/dawn/native/DawnNative.cpp
+++ b/src/dawn/native/DawnNative.cpp
@@ -309,9 +309,4 @@
     return FromAPI(buffer)->GetAllocatedSize();
 }
 
-bool BindGroupLayoutBindingsEqualForTesting(WGPUBindGroupLayout a, WGPUBindGroupLayout b) {
-    bool excludePipelineCompatibiltyToken = true;
-    return FromAPI(a)->IsLayoutEqual(FromAPI(b), excludePipelineCompatibiltyToken);
-}
-
 }  // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index b4bad4b..5fdbaf4 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -63,7 +63,7 @@
 
 struct DeviceBase::Caches {
     ContentLessObjectCache<AttachmentState> attachmentStates;
-    ContentLessObjectCache<BindGroupLayoutBase> bindGroupLayouts;
+    ContentLessObjectCache<BindGroupLayoutInternalBase> bindGroupLayouts;
     ContentLessObjectCache<ComputePipelineBase> computePipelines;
     ContentLessObjectCache<PipelineLayoutBase> pipelineLayouts;
     ContentLessObjectCache<RenderPipelineBase> renderPipelines;
@@ -799,20 +799,22 @@
 ResultOrError<Ref<BindGroupLayoutBase>> DeviceBase::GetOrCreateBindGroupLayout(
     const BindGroupLayoutDescriptor* descriptor,
     PipelineCompatibilityToken pipelineCompatibilityToken) {
-    BindGroupLayoutBase blueprint(this, descriptor, pipelineCompatibilityToken,
-                                  ApiObjectBase::kUntrackedByDevice);
+    BindGroupLayoutInternalBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice);
 
     const size_t blueprintHash = blueprint.ComputeContentHash();
     blueprint.SetContentHash(blueprintHash);
 
-    return GetOrCreate(
-        mCaches->bindGroupLayouts, &blueprint, [&]() -> ResultOrError<Ref<BindGroupLayoutBase>> {
-            Ref<BindGroupLayoutBase> result;
-            DAWN_TRY_ASSIGN(result,
-                            CreateBindGroupLayoutImpl(descriptor, pipelineCompatibilityToken));
-            result->SetContentHash(blueprintHash);
-            return result;
-        });
+    Ref<BindGroupLayoutInternalBase> internal;
+    DAWN_TRY_ASSIGN(internal, GetOrCreate(mCaches->bindGroupLayouts, &blueprint,
+                                          [&]() -> ResultOrError<Ref<BindGroupLayoutInternalBase>> {
+                                              Ref<BindGroupLayoutInternalBase> result;
+                                              DAWN_TRY_ASSIGN(
+                                                  result, CreateBindGroupLayoutImpl(descriptor));
+                                              result->SetContentHash(blueprintHash);
+                                              return result;
+                                          }));
+    return AcquireRef(
+        new BindGroupLayoutBase(this, descriptor->label, internal, pipelineCompatibilityToken));
 }
 
 // Private function used at initialization
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 684c40f..bf337a8 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -458,9 +458,8 @@
 
     virtual ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) = 0;
-    virtual ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) = 0;
+    virtual ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) = 0;
     virtual ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) = 0;
     virtual ResultOrError<Ref<ExternalTextureBase>> CreateExternalTextureImpl(
         const ExternalTextureDescriptor* descriptor);
diff --git a/src/dawn/native/Forward.h b/src/dawn/native/Forward.h
index bcf07a6..a2529fc 100644
--- a/src/dawn/native/Forward.h
+++ b/src/dawn/native/Forward.h
@@ -29,6 +29,7 @@
 class AdapterBase;
 class BindGroupBase;
 class BindGroupLayoutBase;
+class BindGroupLayoutInternalBase;
 class BufferBase;
 class ComputePipelineBase;
 class CommandBufferBase;
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index 839efe9..fac4693 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -379,7 +379,7 @@
     recorder.Record(mMask);
 
     for (BindGroupIndex group : IterateBitSet(mMask)) {
-        recorder.Record(GetBindGroupLayout(group)->GetContentHash());
+        recorder.Record(GetBindGroupLayout(group)->GetInternalBindGroupLayout()->GetContentHash());
     }
 
     return recorder.GetContentHash();
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 5502912..bea2d7b 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -369,7 +369,7 @@
                                                           SingleShaderStage entryPointStage,
                                                           BindingNumber bindingNumber,
                                                           const ShaderBindingInfo& shaderInfo) {
-    const BindGroupLayoutBase::BindingMap& layoutBindings = layout->GetBindingMap();
+    const BindGroupLayoutInternalBase::BindingMap& layoutBindings = layout->GetBindingMap();
 
     // An external texture binding found in the shader will later be expanded into multiple
     // bindings at compile time. This expansion will have already happened in the bgl - so
diff --git a/src/dawn/native/ToBackend.h b/src/dawn/native/ToBackend.h
index ffc507b..664da57 100644
--- a/src/dawn/native/ToBackend.h
+++ b/src/dawn/native/ToBackend.h
@@ -34,7 +34,7 @@
 };
 
 template <typename BackendTraits>
-struct ToBackendTraits<BindGroupLayoutBase, BackendTraits> {
+struct ToBackendTraits<BindGroupLayoutInternalBase, BackendTraits> {
     using BackendType = typename BackendTraits::BindGroupLayoutType;
 };
 
diff --git a/src/dawn/native/d3d11/BindGroupD3D11.cpp b/src/dawn/native/d3d11/BindGroupD3D11.cpp
index b3b8640..e2e3f85 100644
--- a/src/dawn/native/d3d11/BindGroupD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupD3D11.cpp
@@ -24,7 +24,8 @@
 
 // static
 Ref<BindGroup> BindGroup::Create(Device* device, const BindGroupDescriptor* descriptor) {
-    return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor);
+    return ToBackend(descriptor->layout->GetInternalBindGroupLayout())
+        ->AllocateBindGroup(device, descriptor);
 }
 
 BindGroup::BindGroup(Device* device, const BindGroupDescriptor* descriptor)
@@ -34,7 +35,7 @@
 
 void BindGroup::DestroyImpl() {
     BindGroupBase::DestroyImpl();
-    ToBackend(GetLayout())->DeallocateBindGroup(this);
+    ToBackend(GetLayout()->GetInternalBindGroupLayout())->DeallocateBindGroup(this);
 }
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/BindGroupLayoutD3D11.cpp b/src/dawn/native/d3d11/BindGroupLayoutD3D11.cpp
index bd1d8fd..0cfe3c9 100644
--- a/src/dawn/native/d3d11/BindGroupLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupLayoutD3D11.cpp
@@ -20,15 +20,12 @@
 
 ResultOrError<Ref<BindGroupLayout>> BindGroupLayout::Create(
     Device* device,
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return AcquireRef(new BindGroupLayout(device, descriptor, pipelineCompatibilityToken));
+    const BindGroupLayoutDescriptor* descriptor) {
+    return AcquireRef(new BindGroupLayout(device, descriptor));
 }
 
-BindGroupLayout::BindGroupLayout(Device* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken),
+BindGroupLayout::BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor),
       mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {}
 
 Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
diff --git a/src/dawn/native/d3d11/BindGroupLayoutD3D11.h b/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
index 5f61eef..74ddf96 100644
--- a/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
+++ b/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
@@ -23,20 +23,16 @@
 
 class Device;
 
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
-    static ResultOrError<Ref<BindGroupLayout>> Create(
-        Device* device,
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken);
+    static ResultOrError<Ref<BindGroupLayout>> Create(Device* device,
+                                                      const BindGroupLayoutDescriptor* descriptor);
 
     Ref<BindGroup> AllocateBindGroup(Device* device, const BindGroupDescriptor* descriptor);
     void DeallocateBindGroup(BindGroup* bindGroup);
 
   private:
-    BindGroupLayout(Device* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor);
     ~BindGroupLayout() override = default;
 
     SlabAllocator<BindGroup> mBindGroupAllocator;
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index 74a5cd3..7de44ac 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -229,10 +229,9 @@
     return BindGroup::Create(this, descriptor);
 }
 
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return BindGroupLayout::Create(this, descriptor, pipelineCompatibilityToken);
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return BindGroupLayout::Create(this, descriptor);
 }
 
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index e2d3af9..64366e1 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -90,9 +90,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
         const PipelineLayoutDescriptor* descriptor) override;
diff --git a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
index 8f90985..d1907c0 100644
--- a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
+++ b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
@@ -113,7 +113,8 @@
     const BindingInfoArray& moduleBindingInfo = entryPoint.bindings;
 
     for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
-        const BindGroupLayout* groupLayout = ToBackend(layout->GetBindGroupLayout(group));
+        const BindGroupLayout* groupLayout =
+            ToBackend(layout->GetBindGroupLayout(group)->GetInternalBindGroupLayout());
         const auto& indices = layout->GetBindingIndexInfo()[group];
         const auto& groupBindingInfo = moduleBindingInfo[group];
 
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.cpp b/src/dawn/native/d3d12/BindGroupD3D12.cpp
index f0f8471..56160cf 100644
--- a/src/dawn/native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupD3D12.cpp
@@ -30,7 +30,8 @@
 // static
 ResultOrError<Ref<BindGroup>> BindGroup::Create(Device* device,
                                                 const BindGroupDescriptor* descriptor) {
-    return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor);
+    return ToBackend(descriptor->layout->GetInternalBindGroupLayout())
+        ->AllocateBindGroup(device, descriptor);
 }
 
 BindGroup::BindGroup(Device* device,
@@ -38,7 +39,7 @@
                      uint32_t viewSizeIncrement,
                      const CPUDescriptorHeapAllocation& viewAllocation)
     : BindGroupBase(this, device, descriptor) {
-    BindGroupLayout* bgl = ToBackend(GetLayout());
+    BindGroupLayout* bgl = ToBackend(GetLayout()->GetInternalBindGroupLayout());
 
     mCPUViewAllocation = viewAllocation;
 
@@ -207,12 +208,13 @@
 
 void BindGroup::DestroyImpl() {
     BindGroupBase::DestroyImpl();
-    ToBackend(GetLayout())->DeallocateBindGroup(this, &mCPUViewAllocation);
+    ToBackend(GetLayout()->GetInternalBindGroupLayout())
+        ->DeallocateBindGroup(this, &mCPUViewAllocation);
     ASSERT(!mCPUViewAllocation.IsValid());
 }
 
 bool BindGroup::PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator) {
-    const BindGroupLayout* bgl = ToBackend(GetLayout());
+    const BindGroupLayout* bgl = ToBackend(GetLayout()->GetInternalBindGroupLayout());
 
     const uint32_t descriptorCount = bgl->GetCbvUavSrvDescriptorCount();
     if (descriptorCount == 0 || viewAllocator->IsAllocationStillValid(mGPUViewAllocation)) {
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
index 97f094a..62c66b3 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
@@ -57,17 +57,13 @@
 }  // anonymous namespace
 
 // static
-Ref<BindGroupLayout> BindGroupLayout::Create(
-    Device* device,
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return AcquireRef(new BindGroupLayout(device, descriptor, pipelineCompatibilityToken));
+Ref<BindGroupLayout> BindGroupLayout::Create(Device* device,
+                                             const BindGroupLayoutDescriptor* descriptor) {
+    return AcquireRef(new BindGroupLayout(device, descriptor));
 }
 
-BindGroupLayout::BindGroupLayout(Device* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken),
+BindGroupLayout::BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor),
       mDescriptorHeapOffsets(GetBindingCount()),
       mShaderRegisters(GetBindingCount()),
       mCbvUavSrvDescriptorCount(0),
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.h b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
index 288597f..4d8799b 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
@@ -37,11 +37,9 @@
 static constexpr uint32_t kRegisterSpacePlaceholder =
     D3D12_DRIVER_RESERVED_REGISTER_SPACE_VALUES_START;
 
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
-    static Ref<BindGroupLayout> Create(Device* device,
-                                       const BindGroupLayoutDescriptor* descriptor,
-                                       PipelineCompatibilityToken pipelineCompatibilityToken);
+    static Ref<BindGroupLayout> Create(Device* device, const BindGroupLayoutDescriptor* descriptor);
 
     ResultOrError<Ref<BindGroup>> AllocateBindGroup(Device* device,
                                                     const BindGroupDescriptor* descriptor);
@@ -63,9 +61,7 @@
     const std::vector<D3D12_DESCRIPTOR_RANGE1>& GetSamplerDescriptorRanges() const;
 
   private:
-    BindGroupLayout(Device* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor);
     ~BindGroupLayout() override = default;
 
     // Contains the offset into the descriptor heap for the given resource view. Samplers and
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 6c57af3..8421e9e 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -548,9 +548,10 @@
             return;
         }
 
-        const uint32_t cbvUavSrvCount =
-            ToBackend(group->GetLayout())->GetCbvUavSrvDescriptorCount();
-        const uint32_t samplerCount = ToBackend(group->GetLayout())->GetSamplerDescriptorCount();
+        const uint32_t cbvUavSrvCount = ToBackend(group->GetLayout()->GetInternalBindGroupLayout())
+                                            ->GetCbvUavSrvDescriptorCount();
+        const uint32_t samplerCount = ToBackend(group->GetLayout()->GetInternalBindGroupLayout())
+                                          ->GetSamplerDescriptorCount();
 
         if (cbvUavSrvCount > 0) {
             uint32_t parameterIndex = pipelineLayout->GetCbvUavSrvRootParameterIndex(index);
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 2424692..44ac992 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -392,10 +392,9 @@
     const BindGroupDescriptor* descriptor) {
     return BindGroup::Create(this, descriptor);
 }
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return BindGroupLayout::Create(this, descriptor, pipelineCompatibilityToken);
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return BindGroupLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
     return Buffer::Create(this, descriptor);
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index a2f479e..4d1e3c3 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -173,9 +173,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
         const PipelineLayoutDescriptor* descriptor) override;
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
index 82912e7..f6a8167 100644
--- a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
@@ -157,7 +157,8 @@
 
     size_t rangesCount = 0;
     for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) {
-        const BindGroupLayout* bindGroupLayout = ToBackend(GetBindGroupLayout(group));
+        const BindGroupLayout* bindGroupLayout =
+            ToBackend(GetBindGroupLayout(group)->GetInternalBindGroupLayout());
         rangesCount += bindGroupLayout->GetCbvUavSrvDescriptorRanges().size() +
                        bindGroupLayout->GetSamplerDescriptorRanges().size();
     }
@@ -168,7 +169,8 @@
     uint32_t rangeIndex = 0;
 
     for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) {
-        const BindGroupLayout* bindGroupLayout = ToBackend(GetBindGroupLayout(group));
+        const BindGroupLayout* bindGroupLayout =
+            ToBackend(GetBindGroupLayout(group)->GetInternalBindGroupLayout());
 
         // Set the root descriptor table parameter and copy ranges. Ranges are offset by the
         // bind group index Returns whether or not the parameter was set. A root parameter is
diff --git a/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp b/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
index f1d12e3..4bbad54 100644
--- a/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
+++ b/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
@@ -91,7 +91,7 @@
 ResultOrError<Ref<SamplerHeapCacheEntry>> SamplerHeapCache::GetOrCreate(
     const BindGroup* group,
     StagingDescriptorAllocator* samplerAllocator) {
-    const BindGroupLayout* bgl = ToBackend(group->GetLayout());
+    const BindGroupLayout* bgl = ToBackend(group->GetLayout()->GetInternalBindGroupLayout());
 
     // If a previously created bindgroup used the same samplers, the backing sampler heap
     // allocation can be reused. The packed list of samplers acts as the key to lookup the
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
index abde309..6b97e2d 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
@@ -131,7 +131,8 @@
 
     const BindingInfoArray& moduleBindingInfo = entryPoint.bindings;
     for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
-        const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
+        const BindGroupLayout* bgl =
+            ToBackend(layout->GetBindGroupLayout(group)->GetInternalBindGroupLayout());
         const auto& moduleGroupBindingInfo = moduleBindingInfo[group];
 
         // d3d12::BindGroupLayout packs the bindings per HLSL register-space. We modify
diff --git a/src/dawn/native/metal/BindGroupLayoutMTL.h b/src/dawn/native/metal/BindGroupLayoutMTL.h
index ab2abef..1126c1a 100644
--- a/src/dawn/native/metal/BindGroupLayoutMTL.h
+++ b/src/dawn/native/metal/BindGroupLayoutMTL.h
@@ -23,19 +23,16 @@
 class BindGroup;
 class Device;
 
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
     static Ref<BindGroupLayout> Create(DeviceBase* device,
-                                       const BindGroupLayoutDescriptor* descriptor,
-                                       PipelineCompatibilityToken pipelineCompatibilityToken);
+                                       const BindGroupLayoutDescriptor* descriptor);
 
     Ref<BindGroup> AllocateBindGroup(Device* device, const BindGroupDescriptor* descriptor);
     void DeallocateBindGroup(BindGroup* bindGroup);
 
   private:
-    BindGroupLayout(DeviceBase* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
     ~BindGroupLayout() override;
 
     SlabAllocator<BindGroup> mBindGroupAllocator;
diff --git a/src/dawn/native/metal/BindGroupLayoutMTL.mm b/src/dawn/native/metal/BindGroupLayoutMTL.mm
index 6c1a7ac..a4f5de9 100644
--- a/src/dawn/native/metal/BindGroupLayoutMTL.mm
+++ b/src/dawn/native/metal/BindGroupLayoutMTL.mm
@@ -19,17 +19,13 @@
 namespace dawn::native::metal {
 
 // static
-Ref<BindGroupLayout> BindGroupLayout::Create(
-    DeviceBase* device,
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return AcquireRef(new BindGroupLayout(device, descriptor, pipelineCompatibilityToken));
+Ref<BindGroupLayout> BindGroupLayout::Create(DeviceBase* device,
+                                             const BindGroupLayoutDescriptor* descriptor) {
+    return AcquireRef(new BindGroupLayout(device, descriptor));
 }
 
-BindGroupLayout::BindGroupLayout(DeviceBase* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken),
+BindGroupLayout::BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor),
       mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {}
 
 BindGroupLayout::~BindGroupLayout() = default;
diff --git a/src/dawn/native/metal/BindGroupMTL.mm b/src/dawn/native/metal/BindGroupMTL.mm
index 90b9e23..73a8adf 100644
--- a/src/dawn/native/metal/BindGroupMTL.mm
+++ b/src/dawn/native/metal/BindGroupMTL.mm
@@ -25,12 +25,13 @@
 
 void BindGroup::DestroyImpl() {
     BindGroupBase::DestroyImpl();
-    ToBackend(GetLayout())->DeallocateBindGroup(this);
+    ToBackend(GetLayout()->GetInternalBindGroupLayout())->DeallocateBindGroup(this);
 }
 
 // static
 Ref<BindGroup> BindGroup::Create(Device* device, const BindGroupDescriptor* descriptor) {
-    return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor);
+    return ToBackend(descriptor->layout->GetInternalBindGroupLayout())
+        ->AllocateBindGroup(device, descriptor);
 }
 
 }  // namespace dawn::native::metal
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index ea876a0..0e6d84d 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -97,9 +97,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
         CommandEncoder* encoder,
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 946babf..494e040 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -180,10 +180,9 @@
     const BindGroupDescriptor* descriptor) {
     return BindGroup::Create(this, descriptor);
 }
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return BindGroupLayout::Create(this, descriptor, pipelineCompatibilityToken);
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return BindGroupLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
     return Buffer::Create(this, descriptor);
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index 889492e..0091d23 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -123,7 +123,7 @@
     arrayLengthFromUniform.ubo_binding = {0, kBufferLengthBufferSlot};
 
     for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
-        const BindGroupLayoutBase::BindingMap& bindingMap =
+        const BindGroupLayoutInternalBase::BindingMap& bindingMap =
             layout->GetBindGroupLayout(group)->GetBindingMap();
         for (const auto [bindingNumber, bindingIndex] : bindingMap) {
             const BindingInfo& bindingInfo =
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 89c2b20..9296027 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -149,10 +149,9 @@
     const BindGroupDescriptor* descriptor) {
     return AcquireRef(new BindGroup(this, descriptor));
 }
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return AcquireRef(new BindGroupLayout(this, descriptor, pipelineCompatibilityToken));
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return AcquireRef(new BindGroupLayout(this, descriptor));
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
     DAWN_TRY(IncrementMemoryUsage(descriptor->size));
@@ -312,10 +311,8 @@
 
 // BindGroupLayout
 
-BindGroupLayout::BindGroupLayout(DeviceBase* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) {}
+BindGroupLayout::BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor) {}
 
 // Buffer
 
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 6cacc8f..3f110ec 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -131,9 +131,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
         const ComputePipelineDescriptor* descriptor) override;
@@ -222,11 +221,9 @@
     ~BindGroup() override = default;
 };
 
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
-    BindGroupLayout(DeviceBase* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
 
   private:
     ~BindGroupLayout() override = default;
diff --git a/src/dawn/native/opengl/BindGroupGL.cpp b/src/dawn/native/opengl/BindGroupGL.cpp
index a688c2e..147138b 100644
--- a/src/dawn/native/opengl/BindGroupGL.cpp
+++ b/src/dawn/native/opengl/BindGroupGL.cpp
@@ -21,7 +21,7 @@
 namespace dawn::native::opengl {
 
 MaybeError ValidateGLBindGroupDescriptor(const BindGroupDescriptor* descriptor) {
-    const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
+    const BindGroupLayoutInternalBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
     for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
         const BindGroupEntry& entry = descriptor->entries[i];
 
@@ -53,12 +53,13 @@
 
 void BindGroup::DestroyImpl() {
     BindGroupBase::DestroyImpl();
-    ToBackend(GetLayout())->DeallocateBindGroup(this);
+    ToBackend(GetLayout()->GetInternalBindGroupLayout())->DeallocateBindGroup(this);
 }
 
 // static
 Ref<BindGroup> BindGroup::Create(Device* device, const BindGroupDescriptor* descriptor) {
-    return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor);
+    return ToBackend(descriptor->layout->GetInternalBindGroupLayout())
+        ->AllocateBindGroup(device, descriptor);
 }
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/BindGroupLayoutGL.cpp b/src/dawn/native/opengl/BindGroupLayoutGL.cpp
index b665d17..c0eb56d 100644
--- a/src/dawn/native/opengl/BindGroupLayoutGL.cpp
+++ b/src/dawn/native/opengl/BindGroupLayoutGL.cpp
@@ -16,10 +16,8 @@
 
 namespace dawn::native::opengl {
 
-BindGroupLayout::BindGroupLayout(DeviceBase* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken),
+BindGroupLayout::BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor),
       mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {}
 
 Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
diff --git a/src/dawn/native/opengl/BindGroupLayoutGL.h b/src/dawn/native/opengl/BindGroupLayoutGL.h
index 136b16f..18b1d4e 100644
--- a/src/dawn/native/opengl/BindGroupLayoutGL.h
+++ b/src/dawn/native/opengl/BindGroupLayoutGL.h
@@ -23,11 +23,9 @@
 
 class Device;
 
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
-    BindGroupLayout(DeviceBase* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
 
     Ref<BindGroup> AllocateBindGroup(Device* device, const BindGroupDescriptor* descriptor);
     void DeallocateBindGroup(BindGroup* bindGroup);
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index 35f8dd3..1e59ed3 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -201,10 +201,9 @@
     DAWN_TRY(ValidateGLBindGroupDescriptor(descriptor));
     return BindGroup::Create(this, descriptor);
 }
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return AcquireRef(new BindGroupLayout(this, descriptor, pipelineCompatibilityToken));
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return AcquireRef(new BindGroupLayout(this, descriptor));
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
     return AcquireRef(new Buffer(this, descriptor));
diff --git a/src/dawn/native/opengl/DeviceGL.h b/src/dawn/native/opengl/DeviceGL.h
index c020877..2be89e1 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -99,9 +99,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
         const PipelineLayoutDescriptor* descriptor) override;
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
index 94b5a7d..03fdf56 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
@@ -81,10 +81,8 @@
 // static
 ResultOrError<Ref<BindGroupLayout>> BindGroupLayout::Create(
     Device* device,
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    Ref<BindGroupLayout> bgl =
-        AcquireRef(new BindGroupLayout(device, descriptor, pipelineCompatibilityToken));
+    const BindGroupLayoutDescriptor* descriptor) {
+    Ref<BindGroupLayout> bgl = AcquireRef(new BindGroupLayout(device, descriptor));
     DAWN_TRY(bgl->Initialize());
     return bgl;
 }
@@ -144,16 +142,14 @@
     return {};
 }
 
-BindGroupLayout::BindGroupLayout(DeviceBase* device,
-                                 const BindGroupLayoutDescriptor* descriptor,
-                                 PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken),
+BindGroupLayout::BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor),
       mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {}
 
 BindGroupLayout::~BindGroupLayout() = default;
 
 void BindGroupLayout::DestroyImpl() {
-    BindGroupLayoutBase::DestroyImpl();
+    BindGroupLayoutInternalBase::DestroyImpl();
 
     Device* device = ToBackend(GetDevice());
 
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.h b/src/dawn/native/vulkan/BindGroupLayoutVk.h
index 924e121..f78903a 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.h
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.h
@@ -47,16 +47,12 @@
 // the pools are reused when no longer used. Minimizing the number of descriptor pool allocation
 // is important because creating them can incur GPU memory allocation which is usually an
 // expensive syscall.
-class BindGroupLayout final : public BindGroupLayoutBase {
+class BindGroupLayout final : public BindGroupLayoutInternalBase {
   public:
-    static ResultOrError<Ref<BindGroupLayout>> Create(
-        Device* device,
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken);
+    static ResultOrError<Ref<BindGroupLayout>> Create(Device* device,
+                                                      const BindGroupLayoutDescriptor* descriptor);
 
-    BindGroupLayout(DeviceBase* device,
-                    const BindGroupLayoutDescriptor* descriptor,
-                    PipelineCompatibilityToken pipelineCompatibilityToken);
+    BindGroupLayout(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
 
     VkDescriptorSetLayout GetHandle() const;
 
diff --git a/src/dawn/native/vulkan/BindGroupVk.cpp b/src/dawn/native/vulkan/BindGroupVk.cpp
index e4547fc..fe2f4f0 100644
--- a/src/dawn/native/vulkan/BindGroupVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupVk.cpp
@@ -31,7 +31,8 @@
 // static
 ResultOrError<Ref<BindGroup>> BindGroup::Create(Device* device,
                                                 const BindGroupDescriptor* descriptor) {
-    return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor);
+    return ToBackend(descriptor->layout->GetInternalBindGroupLayout())
+        ->AllocateBindGroup(device, descriptor);
 }
 
 BindGroup::BindGroup(Device* device,
@@ -152,7 +153,8 @@
 
 void BindGroup::DestroyImpl() {
     BindGroupBase::DestroyImpl();
-    ToBackend(GetLayout())->DeallocateBindGroup(this, &mDescriptorSetAllocation);
+    ToBackend(GetLayout()->GetInternalBindGroupLayout())
+        ->DeallocateBindGroup(this, &mDescriptorSetAllocation);
 }
 
 VkDescriptorSet BindGroup::GetHandle() const {
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 399e114..da45500 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -154,10 +154,9 @@
     const BindGroupDescriptor* descriptor) {
     return BindGroup::Create(this, descriptor);
 }
-ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl(
-    const BindGroupLayoutDescriptor* descriptor,
-    PipelineCompatibilityToken pipelineCompatibilityToken) {
-    return BindGroupLayout::Create(this, descriptor, pipelineCompatibilityToken);
+ResultOrError<Ref<BindGroupLayoutInternalBase>> Device::CreateBindGroupLayoutImpl(
+    const BindGroupLayoutDescriptor* descriptor) {
+    return BindGroupLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
     return Buffer::Create(this, descriptor);
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 7117d9c..ef716f6 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -124,9 +124,8 @@
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
-    ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl(
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken) override;
+    ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
+        const BindGroupLayoutDescriptor* descriptor) override;
     ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
         const PipelineLayoutDescriptor* descriptor) override;
diff --git a/src/dawn/native/vulkan/PipelineLayoutVk.cpp b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
index 69b0d23..7bbba52 100644
--- a/src/dawn/native/vulkan/PipelineLayoutVk.cpp
+++ b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
@@ -40,7 +40,8 @@
     std::array<VkDescriptorSetLayout, kMaxBindGroups> setLayouts;
     std::array<const CachedObject*, kMaxBindGroups> cachedObjects;
     for (BindGroupIndex setIndex : IterateBitSet(GetBindGroupLayoutsMask())) {
-        const BindGroupLayoutBase* bindGroupLayout = GetBindGroupLayout(setIndex);
+        const BindGroupLayoutInternalBase* bindGroupLayout =
+            GetBindGroupLayout(setIndex)->GetInternalBindGroupLayout();
         setLayouts[numSetLayouts] = ToBackend(bindGroupLayout)->GetHandle();
         cachedObjects[numSetLayouts] = bindGroupLayout;
         numSetLayouts++;
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index 2fe207b..e5fe7c9 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -215,7 +215,8 @@
         GetEntryPoint(programmableStage.entryPoint.c_str()).bindings;
 
     for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
-        const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
+        const BindGroupLayout* bgl =
+            ToBackend(layout->GetBindGroupLayout(group)->GetInternalBindGroupLayout());
         const auto& groupBindingInfo = moduleBindingInfo[group];
         for (const auto& [binding, _] : groupBindingInfo) {
             BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
diff --git a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
index 3bc14e5..de22766 100644
--- a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
+++ b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
@@ -89,7 +89,7 @@
 
 TEST_F(DestroyObjectTests, BindGroupNativeExplicit) {
     BindGroupDescriptor desc = {};
-    desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+    desc.layout = mDeviceMock->GetEmptyBindGroupLayout();
     desc.entryCount = 0;
     desc.entries = nullptr;
 
@@ -105,7 +105,7 @@
 // will also complain if there is a memory leak.
 TEST_F(DestroyObjectTests, BindGroupImplicit) {
     BindGroupDescriptor desc = {};
-    desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+    desc.layout = mDeviceMock->GetEmptyBindGroupLayout();
     desc.entryCount = 0;
     desc.entries = nullptr;
 
@@ -447,7 +447,7 @@
 TEST_F(DestroyObjectTests, PipelineLayoutNativeExplicit) {
     PipelineLayoutDescriptor desc = {};
     std::vector<BindGroupLayoutBase*> bindGroupLayouts;
-    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayout());
     desc.bindGroupLayoutCount = bindGroupLayouts.size();
     desc.bindGroupLayouts = bindGroupLayouts.data();
 
@@ -465,7 +465,7 @@
 TEST_F(DestroyObjectTests, PipelineLayoutImplicit) {
     PipelineLayoutDescriptor desc = {};
     std::vector<BindGroupLayoutBase*> bindGroupLayouts;
-    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayout());
     desc.bindGroupLayoutCount = bindGroupLayouts.size();
     desc.bindGroupLayouts = bindGroupLayouts.data();
 
@@ -779,7 +779,7 @@
     wgpu::BindGroup bindGroup;
     {
         BindGroupDescriptor desc = {};
-        desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+        desc.layout = mDeviceMock->GetEmptyBindGroupLayout();
         desc.entryCount = 0;
         desc.entries = nullptr;
 
@@ -884,7 +884,7 @@
     {
         PipelineLayoutDescriptor desc = {};
         std::vector<BindGroupLayoutBase*> bindGroupLayouts;
-        bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+        bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayout());
         desc.bindGroupLayoutCount = bindGroupLayouts.size();
         desc.bindGroupLayouts = bindGroupLayouts.data();
 
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
index b4b4173..55a8e27 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
@@ -17,10 +17,11 @@
 namespace dawn::native {
 
 BindGroupLayoutMock::BindGroupLayoutMock(DeviceMock* device,
-                                         const BindGroupLayoutDescriptor* descriptor,
-                                         PipelineCompatibilityToken pipelineCompatibilityToken)
-    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) {
-    ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->BindGroupLayoutBase::DestroyImpl(); });
+                                         const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor) {
+    ON_CALL(*this, DestroyImpl).WillByDefault([this] {
+        this->BindGroupLayoutInternalBase::DestroyImpl();
+    });
 
     SetContentHash(ComputeContentHash());
 }
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
index cc38952..b349223 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
@@ -22,12 +22,9 @@
 
 namespace dawn::native {
 
-class BindGroupLayoutMock : public BindGroupLayoutBase {
+class BindGroupLayoutMock : public BindGroupLayoutInternalBase {
   public:
-    BindGroupLayoutMock(
-        DeviceMock* device,
-        const BindGroupLayoutDescriptor* descriptor,
-        PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0));
+    BindGroupLayoutMock(DeviceMock* device, const BindGroupLayoutDescriptor* descriptor);
     ~BindGroupLayoutMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
index 548e8a8..62c4ccb 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
@@ -43,11 +43,9 @@
                 return AcquireRef(new NiceMock<BindGroupMock>(this, descriptor));
             }));
     ON_CALL(*this, CreateBindGroupLayoutImpl)
-        .WillByDefault(WithArgs<0, 1>([this](const BindGroupLayoutDescriptor* descriptor,
-                                             PipelineCompatibilityToken pipelineCompatibilityToken)
-                                          -> ResultOrError<Ref<BindGroupLayoutBase>> {
-            return AcquireRef(
-                new NiceMock<BindGroupLayoutMock>(this, descriptor, pipelineCompatibilityToken));
+        .WillByDefault(WithArgs<0>([this](const BindGroupLayoutDescriptor* descriptor)
+                                       -> ResultOrError<Ref<BindGroupLayoutInternalBase>> {
+            return AcquireRef(new NiceMock<BindGroupLayoutMock>(this, descriptor));
         }));
     ON_CALL(*this, CreateBufferImpl)
         .WillByDefault(WithArgs<0>(
@@ -125,8 +123,4 @@
     return reinterpret_cast<QueueMock*>(GetQueue());
 }
 
-BindGroupLayoutMock* DeviceMock::GetEmptyBindGroupLayoutMock() {
-    return reinterpret_cast<BindGroupLayoutMock*>(GetEmptyBindGroupLayout());
-}
-
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.h b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
index 76e9475..348f9a3 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -44,7 +44,6 @@
 
     // Mock specific functionality.
     QueueMock* GetQueueMock();
-    BindGroupLayoutMock* GetEmptyBindGroupLayoutMock();
 
     MOCK_METHOD(ResultOrError<Ref<CommandBufferBase>>,
                 CreateCommandBuffer,
@@ -70,9 +69,9 @@
                 CreateBindGroupImpl,
                 (const BindGroupDescriptor*),
                 (override));
-    MOCK_METHOD(ResultOrError<Ref<BindGroupLayoutBase>>,
+    MOCK_METHOD(ResultOrError<Ref<BindGroupLayoutInternalBase>>,
                 CreateBindGroupLayoutImpl,
-                (const BindGroupLayoutDescriptor*, PipelineCompatibilityToken),
+                (const BindGroupLayoutDescriptor*),
                 (override));
     MOCK_METHOD(ResultOrError<Ref<BufferBase>>,
                 CreateBufferImpl,
diff --git a/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
index 74662fd..1004938 100644
--- a/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -14,12 +14,15 @@
 
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 
+#include "dawn/native/BindGroupLayout.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
 
 namespace dawn {
 namespace {
 
+using testing::Not;
+
 class GetBindGroupLayoutTests : public ValidationTest {
   protected:
     wgpu::RenderPipeline RenderPipelineFromFragmentShader(const char* shader) {
@@ -42,10 +45,7 @@
 
 // Test that GetBindGroupLayout returns the same object for the same index
 // and for matching layouts.
-TEST_F(GetBindGroupLayoutTests, SameObject) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
+TEST_F(GetBindGroupLayoutTests, EquivalentBGLs) {
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
@@ -85,17 +85,19 @@
 
     wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
 
-    // The same value is returned for the same index.
-    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(0).Get());
+    // A fully equivalent layout is returned for the same index.
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)));
 
-    // Matching bind group layouts at different indices are the same object.
-    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(1).Get());
+    // Matching bind group layouts at different indices are fully equivalent.
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(pipeline.GetBindGroupLayout(1)));
 
-    // BGLs with different bindings types are different objects.
-    EXPECT_NE(pipeline.GetBindGroupLayout(2).Get(), pipeline.GetBindGroupLayout(3).Get());
+    // BGLs with different bindings types are different.
+    EXPECT_THAT(pipeline.GetBindGroupLayout(2),
+                Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(3))));
 
-    // BGLs with different visibilities are different objects.
-    EXPECT_NE(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(2).Get());
+    // BGLs with different visibilities are different.
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0),
+                Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(2))));
 }
 
 // Test that default BindGroupLayouts cannot be used in the creation of a new PipelineLayout
@@ -117,9 +119,6 @@
 // - shader stage visibility is the stage that adds the binding.
 // - dynamic offsets is false
 TEST_F(GetBindGroupLayoutTests, DefaultShaderStageAndDynamicOffsets) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"(
@@ -141,29 +140,33 @@
     desc.entryCount = 1;
     desc.entries = &binding;
 
-    // Check that an otherwise compatible bind group layout doesn't match one created as part of a
-    // default pipeline layout.
-    binding.buffer.hasDynamicOffset = false;
-    binding.visibility = wgpu::ShaderStage::Fragment;
-    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    // Check that an otherwise compatible bind group layout is not fully equivalent to  one created
+    // as part of a default pipeline layout, but cache equivalent.
+    {
+        binding.buffer.hasDynamicOffset = false;
+        binding.visibility = wgpu::ShaderStage::Fragment;
+        wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc);
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
+        EXPECT_THAT(bgl, Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))));
+    }
 
     // Check that any change in visibility doesn't match.
     binding.visibility = wgpu::ShaderStage::Vertex;
-    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))));
 
     binding.visibility = wgpu::ShaderStage::Compute;
-    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))));
 
     // Check that any change in hasDynamicOffsets doesn't match.
     binding.buffer.hasDynamicOffset = true;
     binding.visibility = wgpu::ShaderStage::Fragment;
-    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))));
 }
 
 TEST_F(GetBindGroupLayoutTests, DefaultTextureSampleType) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayout filteringBGL = utils::MakeBindGroupLayout(
@@ -237,59 +240,57 @@
     };
 
     // Textures not used default to non-filtering
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, unusedTextureFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, unusedTextureFragmentModule).Get(), filteringBGL.Get()));
+    {
+        wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, unusedTextureFragmentModule);
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL));
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL)));
+    }
 
     // Textures used with textureLoad default to non-filtering
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, textureLoadFragmentModule).Get(), nonFilteringBGL.Get()));
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, textureLoadFragmentModule).Get(), filteringBGL.Get()));
+    {
+        wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, textureLoadFragmentModule);
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL));
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL)));
+    }
 
     // Textures used with textureLoad on both stages default to non-filtering
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule).Get(),
-        filteringBGL.Get()));
+    {
+        wgpu::BindGroupLayout bgl =
+            BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule);
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL));
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL)));
+    }
 
     // Textures used with textureSample default to filtering
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, textureSampleFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(emptyVertexModule, textureSampleFragmentModule).Get(), filteringBGL.Get()));
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule).Get(),
-        filteringBGL.Get()));
+    {
+        wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, textureSampleFragmentModule);
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL)));
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL));
+    }
+    {
+        wgpu::BindGroupLayout bgl =
+            BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule);
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL)));
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL));
+    }
 
     // Textures used with both textureLoad and textureSample default to filtering
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule).Get(),
-        filteringBGL.Get()));
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule).Get(),
-        nonFilteringBGL.Get()));
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule).Get(),
-        filteringBGL.Get()));
+    {
+        wgpu::BindGroupLayout bgl =
+            BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule);
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL)));
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL));
+    }
+    {
+        wgpu::BindGroupLayout bgl =
+            BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule);
+        EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL)));
+        EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL));
+    }
 }
 
 // Test GetBindGroupLayout works with a compute pipeline
 TEST_F(GetBindGroupLayoutTests, ComputePipeline) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"(
@@ -320,15 +321,15 @@
     desc.entryCount = 1;
     desc.entries = &binding;
 
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+    // The pipeline bind group should be cache equivalent, but not fully equivalent since it was
+    // default created by the pipeline.
+    wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc);
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))));
+    EXPECT_THAT(bgl, BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
 }
 
 // Test that the binding type matches the shader.
 TEST_F(GetBindGroupLayoutTests, BindingType) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -354,8 +355,8 @@
             @fragment fn main() {
                 var pos : vec4f = ssbo.pos;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
     {
         binding.buffer.type = wgpu::BufferBindingType::Uniform;
@@ -368,8 +369,8 @@
             @fragment fn main() {
                 var pos : vec4f = uniforms.pos;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -383,8 +384,8 @@
             @fragment fn main() {
                 var pos : vec4f = ssbo.pos;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     binding.buffer.type = wgpu::BufferBindingType::Undefined;
@@ -397,8 +398,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -409,8 +410,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     binding.texture.sampleType = wgpu::TextureSampleType::Undefined;
@@ -422,17 +423,14 @@
             @fragment fn main() {
                 _ = mySampler;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 }
 
 // Tests that the external texture binding type matches with a texture_external declared in the
 // shader.
 TEST_F(GetBindGroupLayoutTests, ExternalTextureBindingType) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -450,15 +448,12 @@
             @fragment fn main() {
                _ = myExternalTexture;
             })");
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+    EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
 }
 
 // Test that texture view dimension matches the shader.
 TEST_F(GetBindGroupLayoutTests, ViewDimension) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -478,8 +473,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -490,8 +485,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -502,8 +497,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -514,8 +509,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -526,8 +521,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -538,16 +533,13 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 }
 
 // Test that texture component type matches the shader.
 TEST_F(GetBindGroupLayoutTests, TextureComponentType) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -566,8 +558,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -578,8 +570,8 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -590,16 +582,13 @@
             @fragment fn main() {
                 _ = textureDimensions(myTexture);
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 }
 
 // Test that binding= indices match.
 TEST_F(GetBindGroupLayoutTests, BindingIndices) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -623,8 +612,8 @@
             @fragment fn main() {
                 var pos : vec4f = uniforms.pos;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -638,8 +627,8 @@
             @fragment fn main() {
                 var pos : vec4f = uniforms.pos;
             })");
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     {
@@ -653,8 +642,8 @@
             @fragment fn main() {
                 var pos : vec4f = uniforms.pos;
             })");
-        EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-            device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    Not(BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))));
     }
 }
 
@@ -694,9 +683,6 @@
 
 // Test that minBufferSize is set on the BGL and that the max of the min buffer sizes is used.
 TEST_F(GetBindGroupLayoutTests, MinBufferSize) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::ShaderModule vsModule4 = utils::CreateShaderModule(device, R"(
@@ -765,8 +751,7 @@
         descriptor.vertex.module = vsModule4;
         descriptor.cFragment.module = fsModule4;
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), bgl4.Get()));
+        EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl4));
     }
 
     // Check that the max is taken between 4 and 64.
@@ -774,8 +759,7 @@
         descriptor.vertex.module = vsModule64;
         descriptor.cFragment.module = fsModule4;
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), bgl64.Get()));
+        EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl64));
     }
 
     // Check that the order doesn't change that the max is taken.
@@ -783,16 +767,12 @@
         descriptor.vertex.module = vsModule4;
         descriptor.cFragment.module = fsModule64;
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), bgl64.Get()));
+        EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl64));
     }
 }
 
 // Test that the visibility is correctly aggregated if two stages have the exact same binding.
 TEST_F(GetBindGroupLayoutTests, StageAggregation) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::ShaderModule vsModuleNoSampler = utils::CreateShaderModule(device, R"(
@@ -837,8 +817,8 @@
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
 
         binding.visibility = wgpu::ShaderStage::Vertex;
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), device.CreateBindGroupLayout(&desc).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     // Check with only the fragment shader using the sampler
@@ -848,8 +828,8 @@
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
 
         binding.visibility = wgpu::ShaderStage::Fragment;
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), device.CreateBindGroupLayout(&desc).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 
     // Check with both shaders using the sampler
@@ -859,8 +839,8 @@
         wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
 
         binding.visibility = wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex;
-        EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-            pipeline.GetBindGroupLayout(0).Get(), device.CreateBindGroupLayout(&desc).Get()));
+        EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                    BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
     }
 }
 
@@ -986,9 +966,6 @@
 // Test that unused indices return the empty bind group layout if before the last used index, an
 // error otherwise.
 TEST_F(GetBindGroupLayoutTests, UnusedIndex) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"(
@@ -1009,21 +986,18 @@
 
     wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&desc);
 
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        pipeline.GetBindGroupLayout(0).Get(), emptyBindGroupLayout.Get()));  // Used
-    EXPECT_TRUE(native::BindGroupLayoutBindingsEqualForTesting(
-        pipeline.GetBindGroupLayout(1).Get(), emptyBindGroupLayout.Get()));  // Not Used.
-    EXPECT_FALSE(native::BindGroupLayoutBindingsEqualForTesting(
-        pipeline.GetBindGroupLayout(2).Get(), emptyBindGroupLayout.Get()));  // Used.
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0),
+                Not(BindGroupLayoutCacheEq(emptyBindGroupLayout)));  // Used
+    EXPECT_THAT(pipeline.GetBindGroupLayout(1),
+                BindGroupLayoutCacheEq(emptyBindGroupLayout));  // Not used
+    EXPECT_THAT(pipeline.GetBindGroupLayout(2),
+                Not(BindGroupLayoutCacheEq(emptyBindGroupLayout)));  // Used
     ASSERT_DEVICE_ERROR(pipeline.GetBindGroupLayout(3));  // Past last defined BGL, error!
 }
 
 // Test that after explicitly creating a pipeline with a pipeline layout, calling
 // GetBindGroupLayout reflects the same bind group layouts.
 TEST_F(GetBindGroupLayoutTests, Reflection) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayoutEntry binding = {};
@@ -1066,7 +1040,7 @@
 
     wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
 
-    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), bindGroupLayout.Get());
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(bindGroupLayout));
 }
 
 // Test that fragment output validation is for the correct entryPoint
@@ -1116,9 +1090,6 @@
 
 // Test that a pipeline full of explicitly empty BGLs correctly reflects them.
 TEST_F(GetBindGroupLayoutTests, FullOfEmptyBGLs) {
-    // This test works assuming Dawn Native's object deduplication.
-    // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn
-    // Native.
     DAWN_SKIP_TEST_IF(UsesWire());
 
     wgpu::BindGroupLayout emptyBGL = utils::MakeBindGroupLayout(device, {});
@@ -1134,10 +1105,10 @@
     )");
     wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc);
 
-    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), emptyBGL.Get());
-    EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), emptyBGL.Get());
-    EXPECT_EQ(pipeline.GetBindGroupLayout(2).Get(), emptyBGL.Get());
-    EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBGL.Get());
+    EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(emptyBGL));
+    EXPECT_THAT(pipeline.GetBindGroupLayout(1), BindGroupLayoutEq(emptyBGL));
+    EXPECT_THAT(pipeline.GetBindGroupLayout(2), BindGroupLayoutEq(emptyBGL));
+    EXPECT_THAT(pipeline.GetBindGroupLayout(3), BindGroupLayoutEq(emptyBGL));
 }
 
 }  // anonymous namespace
diff --git a/src/dawn/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp b/src/dawn/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
index a0e219e..3ca4ec4 100644
--- a/src/dawn/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
@@ -24,6 +24,8 @@
 namespace dawn {
 namespace {
 
+using testing::Not;
+
 // Helper for describing bindings throughout the tests
 struct BindingDescriptor {
     uint32_t group;
diff --git a/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp b/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
index b01c69c..493144a 100644
--- a/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
+++ b/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
@@ -22,6 +22,8 @@
 namespace dawn {
 namespace {
 
+using testing::Not;
+
 // These tests works assuming Dawn Native's object deduplication. Comparing the pointer is
 // exploiting an implementation detail of Dawn Native.
 class ObjectCachingTest : public ValidationTest {
@@ -40,8 +42,8 @@
     wgpu::BindGroupLayout otherBgl = utils::MakeBindGroupLayout(
         device, {{1, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::Uniform}});
 
-    EXPECT_NE(bgl.Get(), otherBgl.Get());
-    EXPECT_EQ(bgl.Get(), sameBgl.Get());
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
 // Test that two similar bind group layouts won't refer to the same one if they differ by dynamic.
@@ -53,8 +55,8 @@
     wgpu::BindGroupLayout otherBgl = utils::MakeBindGroupLayout(
         device, {{1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform, false}});
 
-    EXPECT_NE(bgl.Get(), otherBgl.Get());
-    EXPECT_EQ(bgl.Get(), sameBgl.Get());
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
 // Test that two similar bind group layouts won't refer to the same one if they differ by min size.
@@ -66,8 +68,8 @@
     wgpu::BindGroupLayout otherBgl = utils::MakeBindGroupLayout(
         device, {{1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform, false, 4}});
 
-    EXPECT_NE(bgl.Get(), otherBgl.Get());
-    EXPECT_EQ(bgl.Get(), sameBgl.Get());
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
 // Test that two similar bind group layouts won't refer to the same one if they differ by
@@ -80,8 +82,8 @@
     wgpu::BindGroupLayout otherBgl = utils::MakeBindGroupLayout(
         device, {{1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Uint}});
 
-    EXPECT_NE(bgl.Get(), otherBgl.Get());
-    EXPECT_EQ(bgl.Get(), sameBgl.Get());
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
 // Test that two similar bind group layouts won't refer to the same one if they differ by
@@ -95,8 +97,8 @@
         device, {{1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float,
                   wgpu::TextureViewDimension::e2DArray}});
 
-    EXPECT_NE(bgl.Get(), otherBgl.Get());
-    EXPECT_EQ(bgl.Get(), sameBgl.Get());
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
 // Test that PipelineLayouts are correctly deduplicated.
diff --git a/src/dawn/tests/unittests/validation/ValidationTest.h b/src/dawn/tests/unittests/validation/ValidationTest.h
index 318e833..40f35a0 100644
--- a/src/dawn/tests/unittests/validation/ValidationTest.h
+++ b/src/dawn/tests/unittests/validation/ValidationTest.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "dawn/common/Log.h"
+#include "dawn/native/BindGroupLayout.h"
 #include "dawn/native/DawnNative.h"
 #include "dawn/webgpu_cpp.h"
 #include "gmock/gmock.h"
@@ -89,6 +90,20 @@
     } while (0)
 #define EXPECT_DEPRECATION_WARNING(statement) EXPECT_DEPRECATION_WARNINGS(statement, 1)
 
+// Gmock matcher helpers that may be used throughout other tests.
+
+// BindGroupLayouts can either be cache equivalent meaning that they may have different
+// compatibility tokens but same internal layout, or fully equivalent meaning that they have the
+// same token and internal layout. Note that being fully equivalent implies that they are cache
+// equivalent.
+MATCHER_P(BindGroupLayoutCacheEq, other, "") {
+    return dawn::native::FromAPI(arg.Get())->GetInternalBindGroupLayout() ==
+           dawn::native::FromAPI(other.Get())->GetInternalBindGroupLayout();
+}
+MATCHER_P(BindGroupLayoutEq, other, "") {
+    return dawn::native::FromAPI(arg.Get())->IsLayoutEqual(dawn::native::FromAPI(other.Get()));
+}
+
 namespace dawn::utils {
 class WireHelper;
 }  // namespace dawn::utils