Separates BindGroupLayoutInternal into it's own file.

- Updates direct decendants to use new header file.
- Follow CL(s) to remove most if not all proxy functions in
  BindGroupLayout.h and further header cleanups.

Bug: dawn:1933
Change-Id: I20b1cd0713c4dfd0fab96220f74f27f22d007c5e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144301
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Brandon Jones <bajones@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index 2f80783..c3f3e59 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -192,6 +192,8 @@
     "BindGroup.h",
     "BindGroupLayout.cpp",
     "BindGroupLayout.h",
+    "BindGroupLayoutInternal.cpp",
+    "BindGroupLayoutInternal.h",
     "BindGroupTracker.h",
     "BindingInfo.cpp",
     "BindingInfo.h",
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp
index d7dd201..58ad417 100644
--- a/src/dawn/native/BindGroupLayout.cpp
+++ b/src/dawn/native/BindGroupLayout.cpp
@@ -14,655 +14,10 @@
 
 #include "dawn/native/BindGroupLayout.h"
 
-#include <algorithm>
-#include <functional>
-#include <limits>
-#include <set>
-#include <vector>
-
-#include "dawn/common/BitSetIterator.h"
-#include "dawn/native/ChainUtils.h"
-#include "dawn/native/Device.h"
-#include "dawn/native/ObjectBase.h"
-#include "dawn/native/ObjectContentHasher.h"
 #include "dawn/native/ObjectType_autogen.h"
-#include "dawn/native/PerStage.h"
-#include "dawn/native/ValidationUtils_autogen.h"
 
 namespace dawn::native {
 
-namespace {
-MaybeError ValidateStorageTextureFormat(DeviceBase* device,
-                                        wgpu::TextureFormat storageTextureFormat) {
-    const Format* format = nullptr;
-    DAWN_TRY_ASSIGN(format, device->GetInternalFormat(storageTextureFormat));
-
-    ASSERT(format != nullptr);
-    DAWN_INVALID_IF(!format->supportsStorageUsage,
-                    "Texture format (%s) does not support storage textures.", storageTextureFormat);
-
-    return {};
-}
-
-MaybeError ValidateStorageTextureViewDimension(wgpu::TextureViewDimension dimension) {
-    switch (dimension) {
-        case wgpu::TextureViewDimension::Cube:
-        case wgpu::TextureViewDimension::CubeArray:
-            return DAWN_VALIDATION_ERROR("%s texture views cannot be used as storage textures.",
-                                         dimension);
-
-        case wgpu::TextureViewDimension::e1D:
-        case wgpu::TextureViewDimension::e2D:
-        case wgpu::TextureViewDimension::e2DArray:
-        case wgpu::TextureViewDimension::e3D:
-            return {};
-
-        case wgpu::TextureViewDimension::Undefined:
-            break;
-    }
-    UNREACHABLE();
-}
-
-MaybeError ValidateBindGroupLayoutEntry(DeviceBase* device,
-                                        const BindGroupLayoutEntry& entry,
-                                        bool allowInternalBinding) {
-    DAWN_TRY(ValidateShaderStage(entry.visibility));
-
-    int bindingMemberCount = 0;
-
-    if (entry.buffer.type != wgpu::BufferBindingType::Undefined) {
-        bindingMemberCount++;
-        const BufferBindingLayout& buffer = entry.buffer;
-
-        // The kInternalStorageBufferBinding is used internally and not a value
-        // in wgpu::BufferBindingType.
-        if (buffer.type == kInternalStorageBufferBinding) {
-            DAWN_INVALID_IF(!allowInternalBinding, "Internal binding types are disallowed");
-        } else {
-            DAWN_TRY(ValidateBufferBindingType(buffer.type));
-        }
-
-        if (buffer.type == wgpu::BufferBindingType::Storage ||
-            buffer.type == kInternalStorageBufferBinding) {
-            DAWN_INVALID_IF(
-                entry.visibility & wgpu::ShaderStage::Vertex,
-                "Read-write storage buffer binding is used with a visibility (%s) that contains %s "
-                "(note that read-only storage buffer bindings are allowed).",
-                entry.visibility, wgpu::ShaderStage::Vertex);
-        }
-    }
-
-    if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) {
-        bindingMemberCount++;
-        DAWN_TRY(ValidateSamplerBindingType(entry.sampler.type));
-    }
-
-    if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) {
-        bindingMemberCount++;
-        const TextureBindingLayout& texture = entry.texture;
-        // The kInternalResolveAttachmentSampleType is used internally and not a value
-        // in wgpu::TextureSampleType.
-        switch (texture.sampleType) {
-            case kInternalResolveAttachmentSampleType:
-                if (allowInternalBinding) {
-                    break;
-                }
-                // should return validation error.
-                [[fallthrough]];
-            default:
-                DAWN_TRY(ValidateTextureSampleType(texture.sampleType));
-                break;
-        }
-
-        // viewDimension defaults to 2D if left undefined, needs validation otherwise.
-        wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D;
-        if (texture.viewDimension != wgpu::TextureViewDimension::Undefined) {
-            DAWN_TRY(ValidateTextureViewDimension(texture.viewDimension));
-            viewDimension = texture.viewDimension;
-        }
-
-        DAWN_INVALID_IF(texture.multisampled && viewDimension != wgpu::TextureViewDimension::e2D,
-                        "View dimension (%s) for a multisampled texture bindings was not %s.",
-                        viewDimension, wgpu::TextureViewDimension::e2D);
-
-        DAWN_INVALID_IF(
-            texture.multisampled && texture.sampleType == wgpu::TextureSampleType::Float,
-            "Sample type for multisampled texture binding was %s.", wgpu::TextureSampleType::Float);
-    }
-
-    if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
-        bindingMemberCount++;
-        const StorageTextureBindingLayout& storageTexture = entry.storageTexture;
-        DAWN_TRY(ValidateStorageTextureAccess(storageTexture.access));
-        DAWN_TRY(ValidateStorageTextureFormat(device, storageTexture.format));
-
-        // viewDimension defaults to 2D if left undefined, needs validation otherwise.
-        if (storageTexture.viewDimension != wgpu::TextureViewDimension::Undefined) {
-            DAWN_TRY(ValidateTextureViewDimension(storageTexture.viewDimension));
-            DAWN_TRY(ValidateStorageTextureViewDimension(storageTexture.viewDimension));
-        }
-
-        if (storageTexture.access == wgpu::StorageTextureAccess::WriteOnly) {
-            DAWN_INVALID_IF(entry.visibility & wgpu::ShaderStage::Vertex,
-                            "Write-only storage texture binding is used with a visibility (%s) "
-                            "that contains %s.",
-                            entry.visibility, wgpu::ShaderStage::Vertex);
-        }
-    }
-
-    const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-    FindInChain(entry.nextInChain, &externalTextureBindingLayout);
-    if (externalTextureBindingLayout != nullptr) {
-        bindingMemberCount++;
-    }
-
-    DAWN_INVALID_IF(bindingMemberCount == 0,
-                    "BindGroupLayoutEntry had none of buffer, sampler, texture, "
-                    "storageTexture, or externalTexture set");
-
-    DAWN_INVALID_IF(bindingMemberCount != 1,
-                    "BindGroupLayoutEntry had more than one of buffer, sampler, texture, "
-                    "storageTexture, or externalTexture set");
-
-    return {};
-}
-
-BindGroupLayoutEntry CreateSampledTextureBindingForExternalTexture(uint32_t binding,
-                                                                   wgpu::ShaderStage visibility) {
-    BindGroupLayoutEntry entry;
-    entry.binding = binding;
-    entry.visibility = visibility;
-    entry.texture.viewDimension = wgpu::TextureViewDimension::e2D;
-    entry.texture.multisampled = false;
-    entry.texture.sampleType = wgpu::TextureSampleType::Float;
-    return entry;
-}
-
-BindGroupLayoutEntry CreateUniformBindingForExternalTexture(uint32_t binding,
-                                                            wgpu::ShaderStage visibility) {
-    BindGroupLayoutEntry entry;
-    entry.binding = binding;
-    entry.visibility = visibility;
-    entry.buffer.hasDynamicOffset = false;
-    entry.buffer.type = wgpu::BufferBindingType::Uniform;
-    return entry;
-}
-
-std::vector<BindGroupLayoutEntry> ExtractAndExpandBglEntries(
-    const BindGroupLayoutDescriptor* descriptor,
-    BindingCounts* bindingCounts,
-    ExternalTextureBindingExpansionMap* externalTextureBindingExpansions) {
-    std::vector<BindGroupLayoutEntry> expandedOutput;
-
-    // When new bgl entries are created, we use binding numbers larger than
-    // kMaxBindingsPerBindGroup to ensure there are no collisions.
-    uint32_t nextOpenBindingNumberForNewEntry = kMaxBindingsPerBindGroup;
-    for (uint32_t i = 0; i < descriptor->entryCount; i++) {
-        const BindGroupLayoutEntry& entry = descriptor->entries[i];
-        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-        FindInChain(entry.nextInChain, &externalTextureBindingLayout);
-        // External textures are expanded from a texture_external into two sampled texture
-        // bindings and one uniform buffer binding. The original binding number is used
-        // for the first sampled texture.
-        if (externalTextureBindingLayout != nullptr) {
-            for (SingleShaderStage stage : IterateStages(entry.visibility)) {
-                // External textures are not fully implemented, which means that expanding
-                // the external texture at this time will not occupy the same number of
-                // binding slots as defined in the WebGPU specification. Here we prematurely
-                // increment the binding counts for an additional sampled textures and a
-                // sampler so that an external texture will occupy the correct number of
-                // slots for correct validation of shader binding limits.
-                // TODO(dawn:1082): Consider removing this and instead making a change to
-                // the validation.
-                constexpr uint32_t kUnimplementedSampledTexturesPerExternalTexture = 2;
-                constexpr uint32_t kUnimplementedSamplersPerExternalTexture = 1;
-                bindingCounts->perStage[stage].sampledTextureCount +=
-                    kUnimplementedSampledTexturesPerExternalTexture;
-                bindingCounts->perStage[stage].samplerCount +=
-                    kUnimplementedSamplersPerExternalTexture;
-            }
-
-            dawn_native::ExternalTextureBindingExpansion bindingExpansion;
-
-            BindGroupLayoutEntry plane0Entry =
-                CreateSampledTextureBindingForExternalTexture(entry.binding, entry.visibility);
-            bindingExpansion.plane0 = BindingNumber(plane0Entry.binding);
-            expandedOutput.push_back(plane0Entry);
-
-            BindGroupLayoutEntry plane1Entry = CreateSampledTextureBindingForExternalTexture(
-                nextOpenBindingNumberForNewEntry++, entry.visibility);
-            bindingExpansion.plane1 = BindingNumber(plane1Entry.binding);
-            expandedOutput.push_back(plane1Entry);
-
-            BindGroupLayoutEntry paramsEntry = CreateUniformBindingForExternalTexture(
-                nextOpenBindingNumberForNewEntry++, entry.visibility);
-            bindingExpansion.params = BindingNumber(paramsEntry.binding);
-            expandedOutput.push_back(paramsEntry);
-
-            externalTextureBindingExpansions->insert(
-                {BindingNumber(entry.binding), bindingExpansion});
-        } else {
-            expandedOutput.push_back(entry);
-        }
-    }
-
-    return expandedOutput;
-}
-}  // anonymous namespace
-
-MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
-                                             const BindGroupLayoutDescriptor* descriptor,
-                                             bool allowInternalBinding) {
-    DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr");
-
-    std::set<BindingNumber> bindingsSet;
-    BindingCounts bindingCounts = {};
-
-    for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
-        const BindGroupLayoutEntry& entry = descriptor->entries[i];
-        BindingNumber bindingNumber = BindingNumber(entry.binding);
-
-        DAWN_INVALID_IF(bindingNumber >= kMaxBindingsPerBindGroupTyped,
-                        "Binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
-                        uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
-        DAWN_INVALID_IF(bindingsSet.count(bindingNumber) != 0,
-                        "On entries[%u]: binding index (%u) was specified by a previous entry.", i,
-                        entry.binding);
-
-        DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding),
-                         "validating entries[%u]", i);
-
-        IncrementBindingCounts(&bindingCounts, entry);
-
-        bindingsSet.insert(bindingNumber);
-    }
-
-    DAWN_TRY_CONTEXT(ValidateBindingCounts(device->GetLimits(), bindingCounts),
-                     "validating binding counts");
-
-    return {};
-}
-
-namespace {
-
-bool operator!=(const BindingInfo& a, const BindingInfo& b) {
-    if (a.visibility != b.visibility || a.bindingType != b.bindingType) {
-        return true;
-    }
-
-    switch (a.bindingType) {
-        case BindingInfoType::Buffer:
-            return a.buffer.type != b.buffer.type ||
-                   a.buffer.hasDynamicOffset != b.buffer.hasDynamicOffset ||
-                   a.buffer.minBindingSize != b.buffer.minBindingSize;
-        case BindingInfoType::Sampler:
-            return a.sampler.type != b.sampler.type;
-        case BindingInfoType::Texture:
-            return a.texture.sampleType != b.texture.sampleType ||
-                   a.texture.viewDimension != b.texture.viewDimension ||
-                   a.texture.multisampled != b.texture.multisampled;
-        case BindingInfoType::StorageTexture:
-            return a.storageTexture.access != b.storageTexture.access ||
-                   a.storageTexture.viewDimension != b.storageTexture.viewDimension ||
-                   a.storageTexture.format != b.storageTexture.format;
-        case BindingInfoType::ExternalTexture:
-            return false;
-    }
-    UNREACHABLE();
-}
-
-bool IsBufferBinding(const BindGroupLayoutEntry& binding) {
-    return binding.buffer.type != wgpu::BufferBindingType::Undefined;
-}
-
-bool BindingHasDynamicOffset(const BindGroupLayoutEntry& binding) {
-    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
-        return binding.buffer.hasDynamicOffset;
-    }
-    return false;
-}
-
-BindingInfo CreateBindGroupLayoutInfo(const BindGroupLayoutEntry& binding) {
-    BindingInfo bindingInfo;
-    bindingInfo.binding = BindingNumber(binding.binding);
-    bindingInfo.visibility = binding.visibility;
-
-    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Buffer;
-        bindingInfo.buffer = binding.buffer;
-    } else if (binding.sampler.type != wgpu::SamplerBindingType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Sampler;
-        bindingInfo.sampler = binding.sampler;
-    } else if (binding.texture.sampleType != wgpu::TextureSampleType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Texture;
-        bindingInfo.texture = binding.texture;
-
-        if (binding.texture.viewDimension == wgpu::TextureViewDimension::Undefined) {
-            bindingInfo.texture.viewDimension = wgpu::TextureViewDimension::e2D;
-        }
-    } else if (binding.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::StorageTexture;
-        bindingInfo.storageTexture = binding.storageTexture;
-
-        if (binding.storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) {
-            bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D;
-        }
-    } else {
-        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-        FindInChain(binding.nextInChain, &externalTextureBindingLayout);
-        if (externalTextureBindingLayout != nullptr) {
-            bindingInfo.bindingType = BindingInfoType::ExternalTexture;
-        }
-    }
-
-    return bindingInfo;
-}
-
-bool SortBindingsCompare(const BindGroupLayoutEntry& a, const BindGroupLayoutEntry& b) {
-    const bool aIsBuffer = IsBufferBinding(a);
-    const bool bIsBuffer = IsBufferBinding(b);
-    if (aIsBuffer != bIsBuffer) {
-        // Always place buffers first.
-        return aIsBuffer;
-    }
-
-    if (aIsBuffer) {
-        bool aHasDynamicOffset = BindingHasDynamicOffset(a);
-        bool bHasDynamicOffset = BindingHasDynamicOffset(b);
-        ASSERT(bIsBuffer);
-        if (aHasDynamicOffset != bHasDynamicOffset) {
-            // Buffers with dynamic offsets should come before those without.
-            // This makes it easy to iterate over the dynamic buffer bindings
-            // [0, dynamicBufferCount) during validation.
-            return aHasDynamicOffset;
-        }
-        if (aHasDynamicOffset) {
-            ASSERT(bHasDynamicOffset);
-            ASSERT(a.binding != b.binding);
-            // Above, we ensured that dynamic buffers are first. Now, ensure that
-            // dynamic buffer bindings are in increasing order. This is because dynamic
-            // buffer offsets are applied in increasing order of binding number.
-            return a.binding < b.binding;
-        }
-    }
-
-    // This applies some defaults and gives us a single value to check for the binding type.
-    BindingInfo aInfo = CreateBindGroupLayoutInfo(a);
-    BindingInfo bInfo = CreateBindGroupLayoutInfo(b);
-
-    // Sort by type.
-    if (aInfo.bindingType != bInfo.bindingType) {
-        return aInfo.bindingType < bInfo.bindingType;
-    }
-
-    if (a.visibility != b.visibility) {
-        return a.visibility < b.visibility;
-    }
-
-    switch (aInfo.bindingType) {
-        case BindingInfoType::Buffer:
-            if (aInfo.buffer.minBindingSize != bInfo.buffer.minBindingSize) {
-                return aInfo.buffer.minBindingSize < bInfo.buffer.minBindingSize;
-            }
-            break;
-        case BindingInfoType::Sampler:
-            if (aInfo.sampler.type != bInfo.sampler.type) {
-                return aInfo.sampler.type < bInfo.sampler.type;
-            }
-            break;
-        case BindingInfoType::Texture:
-            if (aInfo.texture.multisampled != bInfo.texture.multisampled) {
-                return aInfo.texture.multisampled < bInfo.texture.multisampled;
-            }
-            if (aInfo.texture.viewDimension != bInfo.texture.viewDimension) {
-                return aInfo.texture.viewDimension < bInfo.texture.viewDimension;
-            }
-            if (aInfo.texture.sampleType != bInfo.texture.sampleType) {
-                return aInfo.texture.sampleType < bInfo.texture.sampleType;
-            }
-            break;
-        case BindingInfoType::StorageTexture:
-            if (aInfo.storageTexture.access != bInfo.storageTexture.access) {
-                return aInfo.storageTexture.access < bInfo.storageTexture.access;
-            }
-            if (aInfo.storageTexture.viewDimension != bInfo.storageTexture.viewDimension) {
-                return aInfo.storageTexture.viewDimension < bInfo.storageTexture.viewDimension;
-            }
-            if (aInfo.storageTexture.format != bInfo.storageTexture.format) {
-                return aInfo.storageTexture.format < bInfo.storageTexture.format;
-            }
-            break;
-        case BindingInfoType::ExternalTexture:
-            break;
-    }
-    return a.binding < b.binding;
-}
-
-// This is a utility function to help ASSERT that the BGL-binding comparator places buffers
-// first.
-bool CheckBufferBindingsFirst(ityp::span<BindingIndex, const BindingInfo> bindings) {
-    BindingIndex lastBufferIndex{0};
-    BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max();
-    for (BindingIndex i{0}; i < bindings.size(); ++i) {
-        if (bindings[i].bindingType == BindingInfoType::Buffer) {
-            lastBufferIndex = std::max(i, lastBufferIndex);
-        } else {
-            firstNonBufferIndex = std::min(i, firstNonBufferIndex);
-        }
-    }
-
-    // If there are no buffers, then |lastBufferIndex| is initialized to 0 and
-    // |firstNonBufferIndex| gets set to 0.
-    return firstNonBufferIndex >= lastBufferIndex;
-}
-
-}  // namespace
-
-// BindGroupLayoutInternalBase
-
-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);
-
-    std::sort(sortedBindings.begin(), sortedBindings.end(), SortBindingsCompare);
-
-    for (uint32_t i = 0; i < sortedBindings.size(); ++i) {
-        const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)];
-
-        mBindingInfo.push_back(CreateBindGroupLayoutInfo(binding));
-
-        if (IsBufferBinding(binding)) {
-            // Buffers must be contiguously packed at the start of the binding info.
-            ASSERT(GetBufferCount() == BindingIndex(i));
-        }
-        IncrementBindingCounts(&mBindingCounts, binding);
-
-        const auto& [_, inserted] = mBindingMap.emplace(BindingNumber(binding.binding), i);
-        ASSERT(inserted);
-    }
-    ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()}));
-    ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped);
-}
-
-BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
-    DeviceBase* device,
-    const BindGroupLayoutDescriptor* descriptor)
-    : BindGroupLayoutInternalBase(device, descriptor, kUntrackedByDevice) {
-    GetObjectTrackingList()->Track(this);
-}
-
-BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(DeviceBase* device,
-                                                         ObjectBase::ErrorTag tag,
-                                                         const char* label)
-    : ApiObjectBase(device, tag, label) {}
-
-BindGroupLayoutInternalBase::~BindGroupLayoutInternalBase() = default;
-
-void BindGroupLayoutInternalBase::DestroyImpl() {
-    Uncache();
-}
-
-ObjectType BindGroupLayoutInternalBase::GetType() const {
-    return ObjectType::BindGroupLayout;
-}
-
-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 BindGroupLayoutInternalBase::HasBinding(BindingNumber bindingNumber) const {
-    return mBindingMap.count(bindingNumber) != 0;
-}
-
-BindingIndex BindGroupLayoutInternalBase::GetBindingIndex(BindingNumber bindingNumber) const {
-    ASSERT(!IsError());
-    const auto& it = mBindingMap.find(bindingNumber);
-    ASSERT(it != mBindingMap.end());
-    return it->second;
-}
-
-size_t BindGroupLayoutInternalBase::ComputeContentHash() {
-    ObjectContentHasher recorder;
-
-    // std::map is sorted by key, so two BGLs constructed in different orders
-    // will still record the same.
-    for (const auto [id, index] : mBindingMap) {
-        recorder.Record(id, index);
-
-        const BindingInfo& info = mBindingInfo[index];
-        recorder.Record(info.buffer.hasDynamicOffset, info.visibility, info.bindingType,
-                        info.buffer.type, info.buffer.minBindingSize, info.sampler.type,
-                        info.texture.sampleType, info.texture.viewDimension,
-                        info.texture.multisampled, info.storageTexture.access,
-                        info.storageTexture.format, info.storageTexture.viewDimension);
-    }
-
-    return recorder.GetContentHash();
-}
-
-bool BindGroupLayoutInternalBase::EqualityFunc::operator()(
-    const BindGroupLayoutInternalBase* a,
-    const BindGroupLayoutInternalBase* b) const {
-    return a->IsLayoutEqual(b);
-}
-
-BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const {
-    return mBindingInfo.size();
-}
-
-BindingIndex BindGroupLayoutInternalBase::GetBufferCount() const {
-    return BindingIndex(mBindingCounts.bufferCount);
-}
-
-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 BindGroupLayoutInternalBase::GetUnverifiedBufferCount() const {
-    return mBindingCounts.unverifiedBufferCount;
-}
-
-uint32_t BindGroupLayoutInternalBase::GetExternalTextureBindingCount() const {
-    return mExternalTextureBindingExpansionMap.size();
-}
-
-const BindingCounts& BindGroupLayoutInternalBase::GetBindingCountInfo() const {
-    return mBindingCounts;
-}
-
-const ExternalTextureBindingExpansionMap&
-BindGroupLayoutInternalBase::GetExternalTextureBindingExpansionMap() const {
-    return mExternalTextureBindingExpansionMap;
-}
-
-uint32_t BindGroupLayoutInternalBase::GetUnexpandedBindingCount() const {
-    return mUnexpandedBindingCount;
-}
-
-bool BindGroupLayoutInternalBase::IsLayoutEqual(const BindGroupLayoutInternalBase* other) const {
-    if (GetBindingCount() != other->GetBindingCount()) {
-        return false;
-    }
-    for (BindingIndex i{0}; i < GetBindingCount(); ++i) {
-        if (mBindingInfo[i] != other->mBindingInfo[i]) {
-            return false;
-        }
-    }
-    return mBindingMap == other->mBindingMap;
-}
-
-size_t BindGroupLayoutInternalBase::GetBindingDataSize() const {
-    // | ------ buffer-specific ----------| ------------ object pointers -------------|
-    // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------|
-    // Followed by:
-    // |---------buffer size array--------|
-    // |-uint64_t[mUnverifiedBufferCount]-|
-    size_t objectPointerStart = mBindingCounts.bufferCount * sizeof(BufferBindingData);
-    ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>)));
-    size_t bufferSizeArrayStart = Align(
-        objectPointerStart + mBindingCounts.totalCount * sizeof(Ref<ObjectBase>), sizeof(uint64_t));
-    ASSERT(IsAligned(bufferSizeArrayStart, alignof(uint64_t)));
-    return bufferSizeArrayStart + mBindingCounts.unverifiedBufferCount * sizeof(uint64_t);
-}
-
-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(
-        reinterpret_cast<uint64_t*>(bindings + mBindingCounts.totalCount), sizeof(uint64_t));
-
-    ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData)));
-    ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>)));
-    ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t)));
-
-    return {{bufferData, GetBufferCount()},
-            {bindings, GetBindingCount()},
-            {unverifiedBufferSizes, mBindingCounts.unverifiedBufferCount}};
-}
-
-bool BindGroupLayoutInternalBase::IsStorageBufferBinding(BindingIndex bindingIndex) const {
-    ASSERT(bindingIndex < GetBufferCount());
-    switch (GetBindingInfo(bindingIndex).buffer.type) {
-        case wgpu::BufferBindingType::Uniform:
-            return false;
-        case kInternalStorageBufferBinding:
-        case wgpu::BufferBindingType::Storage:
-        case wgpu::BufferBindingType::ReadOnlyStorage:
-            return true;
-        case wgpu::BufferBindingType::Undefined:
-            break;
-    }
-    UNREACHABLE();
-}
-
-std::string BindGroupLayoutInternalBase::EntriesToString() const {
-    std::string entries = "[";
-    std::string sep = "";
-    const BindGroupLayoutInternalBase::BindingMap& bindingMap = GetBindingMap();
-    for (const auto [bindingNumber, bindingIndex] : bindingMap) {
-        const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
-        entries += absl::StrFormat("%s%s", sep, bindingInfo);
-        sep = ", ";
-    }
-    entries += "]";
-    return entries;
-}
-
 BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
                                          const char* label,
                                          Ref<BindGroupLayoutInternalBase> internal,
diff --git a/src/dawn/native/BindGroupLayout.h b/src/dawn/native/BindGroupLayout.h
index 6bf49bb..b0513c2 100644
--- a/src/dawn/native/BindGroupLayout.h
+++ b/src/dawn/native/BindGroupLayout.h
@@ -15,141 +15,13 @@
 #ifndef SRC_DAWN_NATIVE_BINDGROUPLAYOUT_H_
 #define SRC_DAWN_NATIVE_BINDGROUPLAYOUT_H_
 
-#include <algorithm>
-#include <bitset>
-#include <map>
 #include <string>
 
-#include "dawn/common/Constants.h"
-#include "dawn/common/ContentLessObjectCacheable.h"
-#include "dawn/common/SlabAllocator.h"
-#include "dawn/common/ityp_span.h"
-#include "dawn/common/ityp_vector.h"
-#include "dawn/native/BindingInfo.h"
-#include "dawn/native/CachedObject.h"
-#include "dawn/native/Error.h"
-#include "dawn/native/Forward.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
+#include "dawn/native/IntegerTypes.h"
 #include "dawn/native/ObjectBase.h"
 
-#include "dawn/native/dawn_platform.h"
-
 namespace dawn::native {
-// TODO(dawn:1082): Minor optimization to use BindingIndex instead of BindingNumber
-struct ExternalTextureBindingExpansion {
-    BindingNumber plane0;
-    BindingNumber plane1;
-    BindingNumber params;
-};
-
-using ExternalTextureBindingExpansionMap = std::map<BindingNumber, ExternalTextureBindingExpansion>;
-
-MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
-                                             const BindGroupLayoutDescriptor* descriptor,
-                                             bool allowInternalBinding = false);
-
-// 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 BindGroupLayoutInternalBase : public ApiObjectBase,
-                                    public CachedObject,
-                                    public ContentLessObjectCacheable<BindGroupLayoutInternalBase> {
-  public:
-    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;
-    const BindingMap& GetBindingMap() const;
-    bool HasBinding(BindingNumber bindingNumber) const;
-    BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
-
-    // Functions necessary for the unordered_set<BGLBase*>-based cache.
-    size_t ComputeContentHash() override;
-
-    struct EqualityFunc {
-        bool operator()(const BindGroupLayoutInternalBase* a,
-                        const BindGroupLayoutInternalBase* b) const;
-    };
-
-    BindingIndex GetBindingCount() const;
-    // Returns |BindingIndex| because buffers are packed at the front.
-    BindingIndex GetBufferCount() const;
-    // Returns |BindingIndex| because dynamic buffers are packed at the front.
-    BindingIndex GetDynamicBufferCount() const;
-    uint32_t GetUnverifiedBufferCount() const;
-
-    // Used to get counts and validate them in pipeline layout creation. Other getters
-    // should be used to get typed integer counts.
-    const BindingCounts& GetBindingCountInfo() const;
-
-    uint32_t GetExternalTextureBindingCount() const;
-
-    // Used to specify unpacked external texture binding slots when transforming shader modules.
-    const ExternalTextureBindingExpansionMap& GetExternalTextureBindingExpansionMap() const;
-
-    uint32_t GetUnexpandedBindingCount() const;
-
-    // Tests that the BindingInfo of two bind groups are equal.
-    bool IsLayoutEqual(const BindGroupLayoutInternalBase* other) const;
-
-    struct BufferBindingData {
-        uint64_t offset;
-        uint64_t size;
-    };
-
-    struct BindingDataPointers {
-        ityp::span<BindingIndex, BufferBindingData> const bufferData = {};
-        ityp::span<BindingIndex, Ref<ObjectBase>> const bindings = {};
-        ityp::span<uint32_t, uint64_t> const unverifiedBufferSizes = {};
-    };
-
-    // Compute the amount of space / alignment required to store bindings for a bind group of
-    // this layout.
-    size_t GetBindingDataSize() const;
-    static constexpr size_t GetBindingDataAlignment() {
-        static_assert(alignof(Ref<ObjectBase>) <= alignof(BufferBindingData));
-        return alignof(BufferBindingData);
-    }
-
-    BindingDataPointers ComputeBindingDataPointers(void* dataStart) const;
-
-    bool IsStorageBufferBinding(BindingIndex bindingIndex) const;
-
-    // Returns a detailed string representation of the layout entries for use in error messages.
-    std::string EntriesToString() const;
-
-  protected:
-    void DestroyImpl() override;
-
-    template <typename BindGroup>
-    SlabAllocator<BindGroup> MakeFrontendBindGroupAllocator(size_t size) {
-        return SlabAllocator<BindGroup>(
-            size,                                                                        // bytes
-            Align(sizeof(BindGroup), GetBindingDataAlignment()) + GetBindingDataSize(),  // size
-            std::max(alignof(BindGroup), GetBindingDataAlignment())  // alignment
-        );
-    }
-
-  private:
-    BindGroupLayoutInternalBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
-
-    BindingCounts mBindingCounts = {};
-    ityp::vector<BindingIndex, BindingInfo> mBindingInfo;
-
-    // Map from BindGroupLayoutEntry.binding to packed indices.
-    BindingMap mBindingMap;
-
-    ExternalTextureBindingExpansionMap mExternalTextureBindingExpansionMap;
-
-    uint32_t mUnexpandedBindingCount;
-};
 
 // Wrapper passthrough frontend object that is essentially just a Ref to a backing
 // BindGroupLayoutInternalBase and a pipeline compatibility token.
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
new file mode 100644
index 0000000..eb851f8
--- /dev/null
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -0,0 +1,667 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/native/BindGroupLayoutInternal.h"
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "dawn/common/BitSetIterator.h"
+#include "dawn/native/ChainUtils.h"
+#include "dawn/native/Device.h"
+#include "dawn/native/ObjectBase.h"
+#include "dawn/native/ObjectContentHasher.h"
+#include "dawn/native/ObjectType_autogen.h"
+#include "dawn/native/PerStage.h"
+#include "dawn/native/ValidationUtils_autogen.h"
+
+namespace dawn::native {
+
+namespace {
+MaybeError ValidateStorageTextureFormat(DeviceBase* device,
+                                        wgpu::TextureFormat storageTextureFormat) {
+    const Format* format = nullptr;
+    DAWN_TRY_ASSIGN(format, device->GetInternalFormat(storageTextureFormat));
+
+    ASSERT(format != nullptr);
+    DAWN_INVALID_IF(!format->supportsStorageUsage,
+                    "Texture format (%s) does not support storage textures.", storageTextureFormat);
+
+    return {};
+}
+
+MaybeError ValidateStorageTextureViewDimension(wgpu::TextureViewDimension dimension) {
+    switch (dimension) {
+        case wgpu::TextureViewDimension::Cube:
+        case wgpu::TextureViewDimension::CubeArray:
+            return DAWN_VALIDATION_ERROR("%s texture views cannot be used as storage textures.",
+                                         dimension);
+
+        case wgpu::TextureViewDimension::e1D:
+        case wgpu::TextureViewDimension::e2D:
+        case wgpu::TextureViewDimension::e2DArray:
+        case wgpu::TextureViewDimension::e3D:
+            return {};
+
+        case wgpu::TextureViewDimension::Undefined:
+            break;
+    }
+    UNREACHABLE();
+}
+
+MaybeError ValidateBindGroupLayoutEntry(DeviceBase* device,
+                                        const BindGroupLayoutEntry& entry,
+                                        bool allowInternalBinding) {
+    DAWN_TRY(ValidateShaderStage(entry.visibility));
+
+    int bindingMemberCount = 0;
+
+    if (entry.buffer.type != wgpu::BufferBindingType::Undefined) {
+        bindingMemberCount++;
+        const BufferBindingLayout& buffer = entry.buffer;
+
+        // The kInternalStorageBufferBinding is used internally and not a value
+        // in wgpu::BufferBindingType.
+        if (buffer.type == kInternalStorageBufferBinding) {
+            DAWN_INVALID_IF(!allowInternalBinding, "Internal binding types are disallowed");
+        } else {
+            DAWN_TRY(ValidateBufferBindingType(buffer.type));
+        }
+
+        if (buffer.type == wgpu::BufferBindingType::Storage ||
+            buffer.type == kInternalStorageBufferBinding) {
+            DAWN_INVALID_IF(
+                entry.visibility & wgpu::ShaderStage::Vertex,
+                "Read-write storage buffer binding is used with a visibility (%s) that contains %s "
+                "(note that read-only storage buffer bindings are allowed).",
+                entry.visibility, wgpu::ShaderStage::Vertex);
+        }
+    }
+
+    if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) {
+        bindingMemberCount++;
+        DAWN_TRY(ValidateSamplerBindingType(entry.sampler.type));
+    }
+
+    if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) {
+        bindingMemberCount++;
+        const TextureBindingLayout& texture = entry.texture;
+        // The kInternalResolveAttachmentSampleType is used internally and not a value
+        // in wgpu::TextureSampleType.
+        switch (texture.sampleType) {
+            case kInternalResolveAttachmentSampleType:
+                if (allowInternalBinding) {
+                    break;
+                }
+                // should return validation error.
+                [[fallthrough]];
+            default:
+                DAWN_TRY(ValidateTextureSampleType(texture.sampleType));
+                break;
+        }
+
+        // viewDimension defaults to 2D if left undefined, needs validation otherwise.
+        wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D;
+        if (texture.viewDimension != wgpu::TextureViewDimension::Undefined) {
+            DAWN_TRY(ValidateTextureViewDimension(texture.viewDimension));
+            viewDimension = texture.viewDimension;
+        }
+
+        DAWN_INVALID_IF(texture.multisampled && viewDimension != wgpu::TextureViewDimension::e2D,
+                        "View dimension (%s) for a multisampled texture bindings was not %s.",
+                        viewDimension, wgpu::TextureViewDimension::e2D);
+
+        DAWN_INVALID_IF(
+            texture.multisampled && texture.sampleType == wgpu::TextureSampleType::Float,
+            "Sample type for multisampled texture binding was %s.", wgpu::TextureSampleType::Float);
+    }
+
+    if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
+        bindingMemberCount++;
+        const StorageTextureBindingLayout& storageTexture = entry.storageTexture;
+        DAWN_TRY(ValidateStorageTextureAccess(storageTexture.access));
+        DAWN_TRY(ValidateStorageTextureFormat(device, storageTexture.format));
+
+        // viewDimension defaults to 2D if left undefined, needs validation otherwise.
+        if (storageTexture.viewDimension != wgpu::TextureViewDimension::Undefined) {
+            DAWN_TRY(ValidateTextureViewDimension(storageTexture.viewDimension));
+            DAWN_TRY(ValidateStorageTextureViewDimension(storageTexture.viewDimension));
+        }
+
+        if (storageTexture.access == wgpu::StorageTextureAccess::WriteOnly) {
+            DAWN_INVALID_IF(entry.visibility & wgpu::ShaderStage::Vertex,
+                            "Write-only storage texture binding is used with a visibility (%s) "
+                            "that contains %s.",
+                            entry.visibility, wgpu::ShaderStage::Vertex);
+        }
+    }
+
+    const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
+    FindInChain(entry.nextInChain, &externalTextureBindingLayout);
+    if (externalTextureBindingLayout != nullptr) {
+        bindingMemberCount++;
+    }
+
+    DAWN_INVALID_IF(bindingMemberCount == 0,
+                    "BindGroupLayoutEntry had none of buffer, sampler, texture, "
+                    "storageTexture, or externalTexture set");
+
+    DAWN_INVALID_IF(bindingMemberCount != 1,
+                    "BindGroupLayoutEntry had more than one of buffer, sampler, texture, "
+                    "storageTexture, or externalTexture set");
+
+    return {};
+}
+
+BindGroupLayoutEntry CreateSampledTextureBindingForExternalTexture(uint32_t binding,
+                                                                   wgpu::ShaderStage visibility) {
+    BindGroupLayoutEntry entry;
+    entry.binding = binding;
+    entry.visibility = visibility;
+    entry.texture.viewDimension = wgpu::TextureViewDimension::e2D;
+    entry.texture.multisampled = false;
+    entry.texture.sampleType = wgpu::TextureSampleType::Float;
+    return entry;
+}
+
+BindGroupLayoutEntry CreateUniformBindingForExternalTexture(uint32_t binding,
+                                                            wgpu::ShaderStage visibility) {
+    BindGroupLayoutEntry entry;
+    entry.binding = binding;
+    entry.visibility = visibility;
+    entry.buffer.hasDynamicOffset = false;
+    entry.buffer.type = wgpu::BufferBindingType::Uniform;
+    return entry;
+}
+
+std::vector<BindGroupLayoutEntry> ExtractAndExpandBglEntries(
+    const BindGroupLayoutDescriptor* descriptor,
+    BindingCounts* bindingCounts,
+    ExternalTextureBindingExpansionMap* externalTextureBindingExpansions) {
+    std::vector<BindGroupLayoutEntry> expandedOutput;
+
+    // When new bgl entries are created, we use binding numbers larger than
+    // kMaxBindingsPerBindGroup to ensure there are no collisions.
+    uint32_t nextOpenBindingNumberForNewEntry = kMaxBindingsPerBindGroup;
+    for (uint32_t i = 0; i < descriptor->entryCount; i++) {
+        const BindGroupLayoutEntry& entry = descriptor->entries[i];
+        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
+        FindInChain(entry.nextInChain, &externalTextureBindingLayout);
+        // External textures are expanded from a texture_external into two sampled texture
+        // bindings and one uniform buffer binding. The original binding number is used
+        // for the first sampled texture.
+        if (externalTextureBindingLayout != nullptr) {
+            for (SingleShaderStage stage : IterateStages(entry.visibility)) {
+                // External textures are not fully implemented, which means that expanding
+                // the external texture at this time will not occupy the same number of
+                // binding slots as defined in the WebGPU specification. Here we prematurely
+                // increment the binding counts for an additional sampled textures and a
+                // sampler so that an external texture will occupy the correct number of
+                // slots for correct validation of shader binding limits.
+                // TODO(dawn:1082): Consider removing this and instead making a change to
+                // the validation.
+                constexpr uint32_t kUnimplementedSampledTexturesPerExternalTexture = 2;
+                constexpr uint32_t kUnimplementedSamplersPerExternalTexture = 1;
+                bindingCounts->perStage[stage].sampledTextureCount +=
+                    kUnimplementedSampledTexturesPerExternalTexture;
+                bindingCounts->perStage[stage].samplerCount +=
+                    kUnimplementedSamplersPerExternalTexture;
+            }
+
+            dawn_native::ExternalTextureBindingExpansion bindingExpansion;
+
+            BindGroupLayoutEntry plane0Entry =
+                CreateSampledTextureBindingForExternalTexture(entry.binding, entry.visibility);
+            bindingExpansion.plane0 = BindingNumber(plane0Entry.binding);
+            expandedOutput.push_back(plane0Entry);
+
+            BindGroupLayoutEntry plane1Entry = CreateSampledTextureBindingForExternalTexture(
+                nextOpenBindingNumberForNewEntry++, entry.visibility);
+            bindingExpansion.plane1 = BindingNumber(plane1Entry.binding);
+            expandedOutput.push_back(plane1Entry);
+
+            BindGroupLayoutEntry paramsEntry = CreateUniformBindingForExternalTexture(
+                nextOpenBindingNumberForNewEntry++, entry.visibility);
+            bindingExpansion.params = BindingNumber(paramsEntry.binding);
+            expandedOutput.push_back(paramsEntry);
+
+            externalTextureBindingExpansions->insert(
+                {BindingNumber(entry.binding), bindingExpansion});
+        } else {
+            expandedOutput.push_back(entry);
+        }
+    }
+
+    return expandedOutput;
+}
+}  // anonymous namespace
+
+MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
+                                             const BindGroupLayoutDescriptor* descriptor,
+                                             bool allowInternalBinding) {
+    DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr");
+
+    std::set<BindingNumber> bindingsSet;
+    BindingCounts bindingCounts = {};
+
+    for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
+        const BindGroupLayoutEntry& entry = descriptor->entries[i];
+        BindingNumber bindingNumber = BindingNumber(entry.binding);
+
+        DAWN_INVALID_IF(bindingNumber >= kMaxBindingsPerBindGroupTyped,
+                        "Binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
+                        uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
+        DAWN_INVALID_IF(bindingsSet.count(bindingNumber) != 0,
+                        "On entries[%u]: binding index (%u) was specified by a previous entry.", i,
+                        entry.binding);
+
+        DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding),
+                         "validating entries[%u]", i);
+
+        IncrementBindingCounts(&bindingCounts, entry);
+
+        bindingsSet.insert(bindingNumber);
+    }
+
+    DAWN_TRY_CONTEXT(ValidateBindingCounts(device->GetLimits(), bindingCounts),
+                     "validating binding counts");
+
+    return {};
+}
+
+namespace {
+
+bool operator!=(const BindingInfo& a, const BindingInfo& b) {
+    if (a.visibility != b.visibility || a.bindingType != b.bindingType) {
+        return true;
+    }
+
+    switch (a.bindingType) {
+        case BindingInfoType::Buffer:
+            return a.buffer.type != b.buffer.type ||
+                   a.buffer.hasDynamicOffset != b.buffer.hasDynamicOffset ||
+                   a.buffer.minBindingSize != b.buffer.minBindingSize;
+        case BindingInfoType::Sampler:
+            return a.sampler.type != b.sampler.type;
+        case BindingInfoType::Texture:
+            return a.texture.sampleType != b.texture.sampleType ||
+                   a.texture.viewDimension != b.texture.viewDimension ||
+                   a.texture.multisampled != b.texture.multisampled;
+        case BindingInfoType::StorageTexture:
+            return a.storageTexture.access != b.storageTexture.access ||
+                   a.storageTexture.viewDimension != b.storageTexture.viewDimension ||
+                   a.storageTexture.format != b.storageTexture.format;
+        case BindingInfoType::ExternalTexture:
+            return false;
+    }
+    UNREACHABLE();
+}
+
+bool IsBufferBinding(const BindGroupLayoutEntry& binding) {
+    return binding.buffer.type != wgpu::BufferBindingType::Undefined;
+}
+
+bool BindingHasDynamicOffset(const BindGroupLayoutEntry& binding) {
+    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
+        return binding.buffer.hasDynamicOffset;
+    }
+    return false;
+}
+
+BindingInfo CreateBindGroupLayoutInfo(const BindGroupLayoutEntry& binding) {
+    BindingInfo bindingInfo;
+    bindingInfo.binding = BindingNumber(binding.binding);
+    bindingInfo.visibility = binding.visibility;
+
+    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
+        bindingInfo.bindingType = BindingInfoType::Buffer;
+        bindingInfo.buffer = binding.buffer;
+    } else if (binding.sampler.type != wgpu::SamplerBindingType::Undefined) {
+        bindingInfo.bindingType = BindingInfoType::Sampler;
+        bindingInfo.sampler = binding.sampler;
+    } else if (binding.texture.sampleType != wgpu::TextureSampleType::Undefined) {
+        bindingInfo.bindingType = BindingInfoType::Texture;
+        bindingInfo.texture = binding.texture;
+
+        if (binding.texture.viewDimension == wgpu::TextureViewDimension::Undefined) {
+            bindingInfo.texture.viewDimension = wgpu::TextureViewDimension::e2D;
+        }
+    } else if (binding.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
+        bindingInfo.bindingType = BindingInfoType::StorageTexture;
+        bindingInfo.storageTexture = binding.storageTexture;
+
+        if (binding.storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) {
+            bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D;
+        }
+    } else {
+        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
+        FindInChain(binding.nextInChain, &externalTextureBindingLayout);
+        if (externalTextureBindingLayout != nullptr) {
+            bindingInfo.bindingType = BindingInfoType::ExternalTexture;
+        }
+    }
+
+    return bindingInfo;
+}
+
+bool SortBindingsCompare(const BindGroupLayoutEntry& a, const BindGroupLayoutEntry& b) {
+    const bool aIsBuffer = IsBufferBinding(a);
+    const bool bIsBuffer = IsBufferBinding(b);
+    if (aIsBuffer != bIsBuffer) {
+        // Always place buffers first.
+        return aIsBuffer;
+    }
+
+    if (aIsBuffer) {
+        bool aHasDynamicOffset = BindingHasDynamicOffset(a);
+        bool bHasDynamicOffset = BindingHasDynamicOffset(b);
+        ASSERT(bIsBuffer);
+        if (aHasDynamicOffset != bHasDynamicOffset) {
+            // Buffers with dynamic offsets should come before those without.
+            // This makes it easy to iterate over the dynamic buffer bindings
+            // [0, dynamicBufferCount) during validation.
+            return aHasDynamicOffset;
+        }
+        if (aHasDynamicOffset) {
+            ASSERT(bHasDynamicOffset);
+            ASSERT(a.binding != b.binding);
+            // Above, we ensured that dynamic buffers are first. Now, ensure that
+            // dynamic buffer bindings are in increasing order. This is because dynamic
+            // buffer offsets are applied in increasing order of binding number.
+            return a.binding < b.binding;
+        }
+    }
+
+    // This applies some defaults and gives us a single value to check for the binding type.
+    BindingInfo aInfo = CreateBindGroupLayoutInfo(a);
+    BindingInfo bInfo = CreateBindGroupLayoutInfo(b);
+
+    // Sort by type.
+    if (aInfo.bindingType != bInfo.bindingType) {
+        return aInfo.bindingType < bInfo.bindingType;
+    }
+
+    if (a.visibility != b.visibility) {
+        return a.visibility < b.visibility;
+    }
+
+    switch (aInfo.bindingType) {
+        case BindingInfoType::Buffer:
+            if (aInfo.buffer.minBindingSize != bInfo.buffer.minBindingSize) {
+                return aInfo.buffer.minBindingSize < bInfo.buffer.minBindingSize;
+            }
+            break;
+        case BindingInfoType::Sampler:
+            if (aInfo.sampler.type != bInfo.sampler.type) {
+                return aInfo.sampler.type < bInfo.sampler.type;
+            }
+            break;
+        case BindingInfoType::Texture:
+            if (aInfo.texture.multisampled != bInfo.texture.multisampled) {
+                return aInfo.texture.multisampled < bInfo.texture.multisampled;
+            }
+            if (aInfo.texture.viewDimension != bInfo.texture.viewDimension) {
+                return aInfo.texture.viewDimension < bInfo.texture.viewDimension;
+            }
+            if (aInfo.texture.sampleType != bInfo.texture.sampleType) {
+                return aInfo.texture.sampleType < bInfo.texture.sampleType;
+            }
+            break;
+        case BindingInfoType::StorageTexture:
+            if (aInfo.storageTexture.access != bInfo.storageTexture.access) {
+                return aInfo.storageTexture.access < bInfo.storageTexture.access;
+            }
+            if (aInfo.storageTexture.viewDimension != bInfo.storageTexture.viewDimension) {
+                return aInfo.storageTexture.viewDimension < bInfo.storageTexture.viewDimension;
+            }
+            if (aInfo.storageTexture.format != bInfo.storageTexture.format) {
+                return aInfo.storageTexture.format < bInfo.storageTexture.format;
+            }
+            break;
+        case BindingInfoType::ExternalTexture:
+            break;
+    }
+    return a.binding < b.binding;
+}
+
+// This is a utility function to help ASSERT that the BGL-binding comparator places buffers
+// first.
+bool CheckBufferBindingsFirst(ityp::span<BindingIndex, const BindingInfo> bindings) {
+    BindingIndex lastBufferIndex{0};
+    BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max();
+    for (BindingIndex i{0}; i < bindings.size(); ++i) {
+        if (bindings[i].bindingType == BindingInfoType::Buffer) {
+            lastBufferIndex = std::max(i, lastBufferIndex);
+        } else {
+            firstNonBufferIndex = std::min(i, firstNonBufferIndex);
+        }
+    }
+
+    // If there are no buffers, then |lastBufferIndex| is initialized to 0 and
+    // |firstNonBufferIndex| gets set to 0.
+    return firstNonBufferIndex >= lastBufferIndex;
+}
+
+}  // namespace
+
+// BindGroupLayoutInternalBase
+
+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);
+
+    std::sort(sortedBindings.begin(), sortedBindings.end(), SortBindingsCompare);
+
+    for (uint32_t i = 0; i < sortedBindings.size(); ++i) {
+        const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)];
+
+        mBindingInfo.push_back(CreateBindGroupLayoutInfo(binding));
+
+        if (IsBufferBinding(binding)) {
+            // Buffers must be contiguously packed at the start of the binding info.
+            ASSERT(GetBufferCount() == BindingIndex(i));
+        }
+        IncrementBindingCounts(&mBindingCounts, binding);
+
+        const auto& [_, inserted] = mBindingMap.emplace(BindingNumber(binding.binding), i);
+        ASSERT(inserted);
+    }
+    ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()}));
+    ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped);
+}
+
+BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
+    DeviceBase* device,
+    const BindGroupLayoutDescriptor* descriptor)
+    : BindGroupLayoutInternalBase(device, descriptor, kUntrackedByDevice) {
+    GetObjectTrackingList()->Track(this);
+}
+
+BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(DeviceBase* device,
+                                                         ObjectBase::ErrorTag tag,
+                                                         const char* label)
+    : ApiObjectBase(device, tag, label) {}
+
+BindGroupLayoutInternalBase::~BindGroupLayoutInternalBase() = default;
+
+void BindGroupLayoutInternalBase::DestroyImpl() {
+    Uncache();
+}
+
+ObjectType BindGroupLayoutInternalBase::GetType() const {
+    return ObjectType::BindGroupLayout;
+}
+
+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 BindGroupLayoutInternalBase::HasBinding(BindingNumber bindingNumber) const {
+    return mBindingMap.count(bindingNumber) != 0;
+}
+
+BindingIndex BindGroupLayoutInternalBase::GetBindingIndex(BindingNumber bindingNumber) const {
+    ASSERT(!IsError());
+    const auto& it = mBindingMap.find(bindingNumber);
+    ASSERT(it != mBindingMap.end());
+    return it->second;
+}
+
+size_t BindGroupLayoutInternalBase::ComputeContentHash() {
+    ObjectContentHasher recorder;
+
+    // std::map is sorted by key, so two BGLs constructed in different orders
+    // will still record the same.
+    for (const auto [id, index] : mBindingMap) {
+        recorder.Record(id, index);
+
+        const BindingInfo& info = mBindingInfo[index];
+        recorder.Record(info.buffer.hasDynamicOffset, info.visibility, info.bindingType,
+                        info.buffer.type, info.buffer.minBindingSize, info.sampler.type,
+                        info.texture.sampleType, info.texture.viewDimension,
+                        info.texture.multisampled, info.storageTexture.access,
+                        info.storageTexture.format, info.storageTexture.viewDimension);
+    }
+
+    return recorder.GetContentHash();
+}
+
+bool BindGroupLayoutInternalBase::EqualityFunc::operator()(
+    const BindGroupLayoutInternalBase* a,
+    const BindGroupLayoutInternalBase* b) const {
+    return a->IsLayoutEqual(b);
+}
+
+BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const {
+    return mBindingInfo.size();
+}
+
+BindingIndex BindGroupLayoutInternalBase::GetBufferCount() const {
+    return BindingIndex(mBindingCounts.bufferCount);
+}
+
+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 BindGroupLayoutInternalBase::GetUnverifiedBufferCount() const {
+    return mBindingCounts.unverifiedBufferCount;
+}
+
+uint32_t BindGroupLayoutInternalBase::GetExternalTextureBindingCount() const {
+    return mExternalTextureBindingExpansionMap.size();
+}
+
+const BindingCounts& BindGroupLayoutInternalBase::GetBindingCountInfo() const {
+    return mBindingCounts;
+}
+
+const ExternalTextureBindingExpansionMap&
+BindGroupLayoutInternalBase::GetExternalTextureBindingExpansionMap() const {
+    return mExternalTextureBindingExpansionMap;
+}
+
+uint32_t BindGroupLayoutInternalBase::GetUnexpandedBindingCount() const {
+    return mUnexpandedBindingCount;
+}
+
+bool BindGroupLayoutInternalBase::IsLayoutEqual(const BindGroupLayoutInternalBase* other) const {
+    if (GetBindingCount() != other->GetBindingCount()) {
+        return false;
+    }
+    for (BindingIndex i{0}; i < GetBindingCount(); ++i) {
+        if (mBindingInfo[i] != other->mBindingInfo[i]) {
+            return false;
+        }
+    }
+    return mBindingMap == other->mBindingMap;
+}
+
+size_t BindGroupLayoutInternalBase::GetBindingDataSize() const {
+    // | ------ buffer-specific ----------| ------------ object pointers -------------|
+    // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------|
+    // Followed by:
+    // |---------buffer size array--------|
+    // |-uint64_t[mUnverifiedBufferCount]-|
+    size_t objectPointerStart = mBindingCounts.bufferCount * sizeof(BufferBindingData);
+    ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>)));
+    size_t bufferSizeArrayStart = Align(
+        objectPointerStart + mBindingCounts.totalCount * sizeof(Ref<ObjectBase>), sizeof(uint64_t));
+    ASSERT(IsAligned(bufferSizeArrayStart, alignof(uint64_t)));
+    return bufferSizeArrayStart + mBindingCounts.unverifiedBufferCount * sizeof(uint64_t);
+}
+
+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(
+        reinterpret_cast<uint64_t*>(bindings + mBindingCounts.totalCount), sizeof(uint64_t));
+
+    ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData)));
+    ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>)));
+    ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t)));
+
+    return {{bufferData, GetBufferCount()},
+            {bindings, GetBindingCount()},
+            {unverifiedBufferSizes, mBindingCounts.unverifiedBufferCount}};
+}
+
+bool BindGroupLayoutInternalBase::IsStorageBufferBinding(BindingIndex bindingIndex) const {
+    ASSERT(bindingIndex < GetBufferCount());
+    switch (GetBindingInfo(bindingIndex).buffer.type) {
+        case wgpu::BufferBindingType::Uniform:
+            return false;
+        case kInternalStorageBufferBinding:
+        case wgpu::BufferBindingType::Storage:
+        case wgpu::BufferBindingType::ReadOnlyStorage:
+            return true;
+        case wgpu::BufferBindingType::Undefined:
+            break;
+    }
+    UNREACHABLE();
+}
+
+std::string BindGroupLayoutInternalBase::EntriesToString() const {
+    std::string entries = "[";
+    std::string sep = "";
+    const BindGroupLayoutInternalBase::BindingMap& bindingMap = GetBindingMap();
+    for (const auto [bindingNumber, bindingIndex] : bindingMap) {
+        const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
+        entries += absl::StrFormat("%s%s", sep, bindingInfo);
+        sep = ", ";
+    }
+    entries += "]";
+    return entries;
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/native/BindGroupLayoutInternal.h b/src/dawn/native/BindGroupLayoutInternal.h
new file mode 100644
index 0000000..ec5cf8d
--- /dev/null
+++ b/src/dawn/native/BindGroupLayoutInternal.h
@@ -0,0 +1,156 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_NATIVE_BINDGROUPLAYOUTINTERNAL_H_
+#define SRC_DAWN_NATIVE_BINDGROUPLAYOUTINTERNAL_H_
+
+#include <algorithm>
+#include <bitset>
+#include <map>
+#include <string>
+
+#include "dawn/common/Constants.h"
+#include "dawn/common/ContentLessObjectCacheable.h"
+#include "dawn/common/SlabAllocator.h"
+#include "dawn/common/ityp_span.h"
+#include "dawn/common/ityp_vector.h"
+#include "dawn/native/BindingInfo.h"
+#include "dawn/native/CachedObject.h"
+#include "dawn/native/Error.h"
+#include "dawn/native/Forward.h"
+#include "dawn/native/ObjectBase.h"
+
+#include "dawn/native/dawn_platform.h"
+
+namespace dawn::native {
+// TODO(dawn:1082): Minor optimization to use BindingIndex instead of BindingNumber
+struct ExternalTextureBindingExpansion {
+    BindingNumber plane0;
+    BindingNumber plane1;
+    BindingNumber params;
+};
+
+using ExternalTextureBindingExpansionMap = std::map<BindingNumber, ExternalTextureBindingExpansion>;
+
+MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
+                                             const BindGroupLayoutDescriptor* descriptor,
+                                             bool allowInternalBinding = false);
+
+// 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 BindGroupLayoutInternalBase : public ApiObjectBase,
+                                    public CachedObject,
+                                    public ContentLessObjectCacheable<BindGroupLayoutInternalBase> {
+  public:
+    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;
+    const BindingMap& GetBindingMap() const;
+    bool HasBinding(BindingNumber bindingNumber) const;
+    BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
+
+    // Functions necessary for the unordered_set<BGLBase*>-based cache.
+    size_t ComputeContentHash() override;
+
+    struct EqualityFunc {
+        bool operator()(const BindGroupLayoutInternalBase* a,
+                        const BindGroupLayoutInternalBase* b) const;
+    };
+
+    BindingIndex GetBindingCount() const;
+    // Returns |BindingIndex| because buffers are packed at the front.
+    BindingIndex GetBufferCount() const;
+    // Returns |BindingIndex| because dynamic buffers are packed at the front.
+    BindingIndex GetDynamicBufferCount() const;
+    uint32_t GetUnverifiedBufferCount() const;
+
+    // Used to get counts and validate them in pipeline layout creation. Other getters
+    // should be used to get typed integer counts.
+    const BindingCounts& GetBindingCountInfo() const;
+
+    uint32_t GetExternalTextureBindingCount() const;
+
+    // Used to specify unpacked external texture binding slots when transforming shader modules.
+    const ExternalTextureBindingExpansionMap& GetExternalTextureBindingExpansionMap() const;
+
+    uint32_t GetUnexpandedBindingCount() const;
+
+    // Tests that the BindingInfo of two bind groups are equal.
+    bool IsLayoutEqual(const BindGroupLayoutInternalBase* other) const;
+
+    struct BufferBindingData {
+        uint64_t offset;
+        uint64_t size;
+    };
+
+    struct BindingDataPointers {
+        ityp::span<BindingIndex, BufferBindingData> const bufferData = {};
+        ityp::span<BindingIndex, Ref<ObjectBase>> const bindings = {};
+        ityp::span<uint32_t, uint64_t> const unverifiedBufferSizes = {};
+    };
+
+    // Compute the amount of space / alignment required to store bindings for a bind group of
+    // this layout.
+    size_t GetBindingDataSize() const;
+    static constexpr size_t GetBindingDataAlignment() {
+        static_assert(alignof(Ref<ObjectBase>) <= alignof(BufferBindingData));
+        return alignof(BufferBindingData);
+    }
+
+    BindingDataPointers ComputeBindingDataPointers(void* dataStart) const;
+
+    bool IsStorageBufferBinding(BindingIndex bindingIndex) const;
+
+    // Returns a detailed string representation of the layout entries for use in error messages.
+    std::string EntriesToString() const;
+
+  protected:
+    void DestroyImpl() override;
+
+    template <typename BindGroup>
+    SlabAllocator<BindGroup> MakeFrontendBindGroupAllocator(size_t size) {
+        return SlabAllocator<BindGroup>(
+            size,                                                                        // bytes
+            Align(sizeof(BindGroup), GetBindingDataAlignment()) + GetBindingDataSize(),  // size
+            std::max(alignof(BindGroup), GetBindingDataAlignment())  // alignment
+        );
+    }
+
+  private:
+    BindGroupLayoutInternalBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
+
+    BindingCounts mBindingCounts = {};
+    ityp::vector<BindingIndex, BindingInfo> mBindingInfo;
+
+    // Map from BindGroupLayoutEntry.binding to packed indices.
+    BindingMap mBindingMap;
+
+    ExternalTextureBindingExpansionMap mExternalTextureBindingExpansionMap;
+
+    uint32_t mUnexpandedBindingCount;
+};
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_BINDGROUPLAYOUT_H_
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 6809257..3c2a231 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -44,6 +44,8 @@
     "BindGroup.h"
     "BindGroupLayout.cpp"
     "BindGroupLayout.h"
+    "BindGroupLayoutInternal.cpp"
+    "BindGroupLayoutInternal.h"
     "BindGroupTracker.h"
     "BindingInfo.cpp"
     "BindingInfo.h"
diff --git a/src/dawn/native/d3d11/BindGroupLayoutD3D11.h b/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
index 74ddf96..59d45d2 100644
--- a/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
+++ b/src/dawn/native/d3d11/BindGroupLayoutD3D11.h
@@ -16,7 +16,7 @@
 #define SRC_DAWN_NATIVE_D3D11_BINDGROUPLAYOUTD3D11_H_
 
 #include "dawn/common/SlabAllocator.h"
-#include "dawn/native/BindGroupLayout.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/d3d11/BindGroupD3D11.h"
 
 namespace dawn::native::d3d11 {
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.h b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
index 4d8799b..d5df9ff 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
@@ -17,10 +17,9 @@
 
 #include <vector>
 
-#include "dawn/native/BindGroupLayout.h"
-
 #include "dawn/common/SlabAllocator.h"
 #include "dawn/common/ityp_stack_vec.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/d3d12/BindGroupD3D12.h"
 #include "dawn/native/d3d12/d3d12_platform.h"
 
diff --git a/src/dawn/native/metal/BindGroupLayoutMTL.h b/src/dawn/native/metal/BindGroupLayoutMTL.h
index 1126c1a..8c859a7 100644
--- a/src/dawn/native/metal/BindGroupLayoutMTL.h
+++ b/src/dawn/native/metal/BindGroupLayoutMTL.h
@@ -16,7 +16,7 @@
 #define SRC_DAWN_NATIVE_METAL_BINDGROUPLAYOUTMTL_H_
 
 #include "dawn/common/SlabAllocator.h"
-#include "dawn/native/BindGroupLayout.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 
 namespace dawn::native::metal {
 
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 3f110ec..97b0bb2 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -19,7 +19,7 @@
 #include <vector>
 
 #include "dawn/native/BindGroup.h"
-#include "dawn/native/BindGroupLayout.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/Buffer.h"
 #include "dawn/native/CommandBuffer.h"
 #include "dawn/native/CommandEncoder.h"
diff --git a/src/dawn/native/opengl/BindGroupLayoutGL.h b/src/dawn/native/opengl/BindGroupLayoutGL.h
index 18b1d4e..b77229a 100644
--- a/src/dawn/native/opengl/BindGroupLayoutGL.h
+++ b/src/dawn/native/opengl/BindGroupLayoutGL.h
@@ -16,7 +16,7 @@
 #define SRC_DAWN_NATIVE_OPENGL_BINDGROUPLAYOUTGL_H_
 
 #include "dawn/common/SlabAllocator.h"
-#include "dawn/native/BindGroupLayout.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/opengl/BindGroupGL.h"
 
 namespace dawn::native::opengl {
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.h b/src/dawn/native/vulkan/BindGroupLayoutVk.h
index f78903a..1aa6ff2 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.h
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.h
@@ -17,10 +17,9 @@
 
 #include <vector>
 
-#include "dawn/native/BindGroupLayout.h"
-
 #include "dawn/common/SlabAllocator.h"
 #include "dawn/common/vulkan_platform.h"
+#include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/vulkan/BindGroupVk.h"
 
 namespace dawn::native {