[dawn][native] Store ExternalTexture in BGL BindingInfo

Store both internal bindings and external bindings in the BGL. This is
the new comment at the top of the class that explains the newly added
APIBindingIndex as well.

<copypaste>
BindGroupLayout stores the information passed in the BindGroupLayoutDescriptor
but processes it in various ways to make it more efficient to use internally
and to add internal bindings used to implement WebGPU feature that don't exist
in backend APIs.

Storing information in hashmap<BindingNumber, T> would be inefficient because
these numbers may be sparse. Instead the are compacted into vectors, and
reordered using |BindingTypeOrder| to make it efficient to iterate over all the
bindings of a same kind. Indexind the packed bindings is done with
|BindingIndex| or |APIBindingIndex| (see below for the explanation).

In other cases bindings need to be expanded because a single BGLEntry can
match multiple BGEntries when bindingArraySize > 1. To make handling more
regular, a fake BGLEntry is created for each array element such that most code
doesn't need to be aware of bindingArraySize.

We also need to have private bindings that cannot be set by the users: dynamic
binding array or ExternalTextures add additional bindings for their inner
workings which are private to Dawn. Conversely ExternalTexture is a pure
frontend object and doesn't exist in backends, so dawn::native must mostly be
unaware about it. This is where |BindingIndex| and |APIBindingIndex| are
different:

 - |APIBindingIndex| are user-facing bindings and cannot be used to access
 private bindings. It is used in code for BindGroup validation and opertations
 and when reflecting/validating WGSL bind points.
 - |BindingIndex| are Dawn-facing bindings where ExternalTexture shouldn't be
 accessed and instead can be used to access internal bindings.

Internally both |APIBindingIndex| and |BindingIndex| are used to access the
same vector, but the types are used to force uses of different BindGroupLayout
accessors that ASSERT the invariant above.
</copypaste>

ShaderModule in every backend is modified to iterate over the BGL's
BindingInfo using the BindingMap. Previously they iterated over the list
of bindings in the EntryPointMetadata. Iterating over the BGL's entries
allow using the new ExternalTextureBindingInfo that points at the
BindingIndex of its plane0/1/metadata, but requires converting from
APIBindingIndex to BindingIndex. (in future CLs the GenerateBindings can
be factored between all the backends)

The OpenGL ShaderModule is modified further to use a BindingIndex
instead of a BindingNumber in the CombinedSamplerPair.

A few additional cleanups will be done in follow-up CLs:
 - The external texture expansion map will be removed.
 - BGL::GetBindingIndex(BindingNumber) will be removed.

Bug: 42240282
Change-Id: I745d6dcaa1368be2656e09525e77edd0a34709bb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/262196
Commit-Queue: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 92de745..b51713c 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -387,21 +387,11 @@
 MaybeError ValidateExternalTextureBinding(
     const DeviceBase* device,
     const BindGroupEntry& entry,
-    const ExternalTextureBindingEntry* externalTextureBindingEntry,
-    const ExternalTextureBindingExpansionMap& expansions) {
-    DAWN_INVALID_IF(externalTextureBindingEntry == nullptr,
-                    "Binding entry external texture not set.");
-
+    const ExternalTextureBindingEntry* externalTextureBindingEntry) {
     DAWN_INVALID_IF(
         entry.sampler != nullptr || entry.textureView != nullptr || entry.buffer != nullptr,
         "Expected only external texture to be set for binding entry.");
-
-    DAWN_INVALID_IF(!expansions.contains(BindingNumber(entry.binding)),
-                    "External texture binding entry %u is not present in the bind group layout.",
-                    entry.binding);
-
     DAWN_TRY(device->ValidateObject(externalTextureBindingEntry->externalTexture));
-
     return {};
 }
 
@@ -458,7 +448,7 @@
     for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
         const BindGroupEntry& entry = descriptor->entries[i];
         const BindingInfo& bindingInfo =
-            layout->GetBindingInfo(bindingMap.at(BindingNumber(entry.binding)));
+            layout->GetAPIBindingInfo(bindingMap.at(BindingNumber(entry.binding)));
         if (std::holds_alternative<TextureBindingInfo>(bindingInfo.bindingLayout) &&
             entry.textureView && entry.textureView->IsYCbCr()) {
             DAWN_INVALID_IF(!sampledYcbcrTextures.test(i),
@@ -560,7 +550,7 @@
     // needed.
     uint32_t staticEntryCount = 0;
     bool needsCrossBindingValidation = layout->NeedsCrossBindingValidation();
-    ityp::bitset<BindingIndex, kMaxBindingsPerPipelineLayout> bindingsSet;
+    ityp::bitset<APIBindingIndex, kMaxBindingsPerPipelineLayout> bindingsSet;
     for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
         const BindGroupEntry& entry = descriptor->entries[i];
         BindingNumber binding = BindingNumber(entry.binding);
@@ -581,13 +571,13 @@
         staticEntryCount++;
 
         // Check for redundant static entries.
-        BindingIndex bindingIndex = it->second;
+        APIBindingIndex bindingIndex = it->second;
         DAWN_INVALID_IF(bindingsSet[bindingIndex],
                         "In entries[%u], binding index %u already used by a previous entry", i,
                         binding);
         bindingsSet.set(bindingIndex);
 
-        const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);
+        const BindingInfo& bindingInfo = layout->GetAPIBindingInfo(bindingIndex);
 
         // Below this block we validate entries based on the bind group layout, in which
         // external textures have been expanded into their underlying contents. For this reason
@@ -599,20 +589,6 @@
         UnpackedPtr<BindGroupEntry> unpacked;
         DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(&entry));
 
-        if (layout->GetExternalTextureBindingExpansionMap().contains(binding)) {
-            if (auto* externalTextureBindingEntry = unpacked.Get<ExternalTextureBindingEntry>()) {
-                DAWN_TRY(ValidateExternalTextureBinding(
-                    device, entry, externalTextureBindingEntry,
-                    layout->GetExternalTextureBindingExpansionMap()));
-                continue;
-            }
-                DAWN_TRY_CONTEXT(ValidateTextureViewBindingUsedAsExternalTexture(device, entry),
-                                 "validating entries[%u] as a TextureView."
-                                 "\nExpected entry layout: %s",
-                                 i, layout);
-                continue;
-        }
-
         const TexelBufferBindingEntry* texelBufferEntry = unpacked.Get<TexelBufferBindingEntry>();
         DAWN_INVALID_IF(
             texelBufferEntry != nullptr &&
@@ -652,7 +628,14 @@
                 [&](const StaticSamplerBindingInfo& layout) -> MaybeError {
                     return DAWN_VALIDATION_ERROR("An entry is provided for a static sampler.");
                 },
-                [&](const ExternalTextureBindingInfo& layout) -> MaybeError { DAWN_UNREACHABLE(); },
+                [&](const ExternalTextureBindingInfo& layout) -> MaybeError {
+                    if (auto* externalTextureBindingEntry =
+                            unpacked.Get<ExternalTextureBindingEntry>()) {
+                        return ValidateExternalTextureBinding(device, entry,
+                                                              externalTextureBindingEntry);
+                    }
+                    return ValidateTextureViewBindingUsedAsExternalTexture(device, entry);
+                },
 
                 [](const InputAttachmentBindingInfo&) -> MaybeError {
                     // Internal use only. No validation.
@@ -734,108 +717,101 @@
             continue;
         }
 
-        BindingIndex bindingIndex = layout->GetBindingIndex(binding);
-        DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
+        APIBindingIndex apiBindingIndex = layout->GetAPIBindingIndex(binding);
 
-        // Only a single binding type should be set, so once we found it we can skip to the
-        // next loop iteration.
+        DAWN_TRY(MatchVariant(
+            layout->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
+            [&](const BufferBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
+                DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+                mBindingData.bindings[bindingIndex] = entry->buffer;
+                mBindingData.bufferData[bindingIndex].offset = entry->offset;
+                uint64_t bufferSize = (entry->size == wgpu::kWholeSize)
+                                          ? entry->buffer->GetSize() - entry->offset
+                                          : entry->size;
+                mBindingData.bufferData[bindingIndex].size = bufferSize;
+                return {};
+            },
 
-        if (entry->buffer != nullptr) {
-            DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = entry->buffer;
-            mBindingData.bufferData[bindingIndex].offset = entry->offset;
-            uint64_t bufferSize = (entry->size == wgpu::kWholeSize)
-                                      ? entry->buffer->GetSize() - entry->offset
-                                      : entry->size;
-            mBindingData.bufferData[bindingIndex].size = bufferSize;
-            continue;
-        }
-
-        if (entry->textureView != nullptr) {
-            // TODO(42240282): Store external textures in
-            // BindGroupLayoutBase::BindingDataPointers::bindings so that we can have a MatchVariant
-            // that contains the ExternalTextureInfo.
-            ExternalTextureBindingExpansionMap expansions =
-                layout->GetExternalTextureBindingExpansionMap();
-            ExternalTextureBindingExpansionMap::iterator it =
-                expansions.find(BindingNumber(entry->binding));
-
-            if (it == expansions.end()) {
+            [&](const TextureBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
                 DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
                 mBindingData.bindings[bindingIndex] = entry->textureView;
-            } else {
+                return {};
+            },
+            [&](const StorageTextureBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
+                DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+                mBindingData.bindings[bindingIndex] = entry->textureView;
+                return {};
+            },
+            [&](const InputAttachmentBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
+                DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+                mBindingData.bindings[bindingIndex] = entry->textureView;
+                return {};
+            },
+            [&](const SamplerBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
+                DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+                mBindingData.bindings[bindingIndex] = entry->sampler;
+                return {};
+            },
+            [&](const TexelBufferBindingInfo&) -> MaybeError {
+                BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
+                DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+                auto* texelBufferBindingEntry = entry.Get<TexelBufferBindingEntry>();
+                mBindingData.bindings[bindingIndex] = texelBufferBindingEntry->texelBufferView;
+                return {};
+            },
+
+            [&](const ExternalTextureBindingInfo& info) -> MaybeError {
+                // Here we unpack external texture bindings into multiple additional bindings for
+                // the external texture's contents. New binding locations previously determined in
+                // the bind group layout are created in this bind group and filled with the external
+                // texture's underlying resources.
+                if (auto* externalTextureBindingEntry = entry.Get<ExternalTextureBindingEntry>()) {
+                    mBoundExternalTextures.push_back(externalTextureBindingEntry->externalTexture);
+
+                    DAWN_ASSERT(mBindingData.bindings[info.plane0] == nullptr);
+                    mBindingData.bindings[info.plane0] =
+                        externalTextureBindingEntry->externalTexture->GetTextureViews()[0];
+
+                    DAWN_ASSERT(mBindingData.bindings[info.plane1] == nullptr);
+                    mBindingData.bindings[info.plane1] =
+                        externalTextureBindingEntry->externalTexture->GetTextureViews()[1];
+
+                    DAWN_ASSERT(mBindingData.bindings[info.params] == nullptr);
+                    mBindingData.bindings[info.params] =
+                        externalTextureBindingEntry->externalTexture->GetParamsBuffer();
+                    mBindingData.bufferData[info.params].offset = 0;
+                    mBindingData.bufferData[info.params].size =
+                        sizeof(dawn::native::ExternalTextureParams);
+                    return {};
+                }
+
                 // If this is for a texture view that is used as an external texture, we need to
                 // also provide placeholder for the second plane and a parameter buffer.
-                BindingIndex plane0BindingIndex = layout->GetBindingIndex(it->second.plane0);
-                BindingIndex plane1BindingIndex = layout->GetBindingIndex(it->second.plane1);
-                BindingIndex paramsBindingIndex = layout->GetBindingIndex(it->second.params);
+                DAWN_ASSERT(entry->textureView != nullptr);
 
-                DAWN_ASSERT(mBindingData.bindings[plane0BindingIndex] == nullptr);
-                mBindingData.bindings[plane0BindingIndex] = entry->textureView;
+                DAWN_ASSERT(mBindingData.bindings[info.plane0] == nullptr);
+                mBindingData.bindings[info.plane0] = entry->textureView;
 
-                DAWN_ASSERT(mBindingData.bindings[plane1BindingIndex] == nullptr);
-                DAWN_TRY_ASSIGN(mBindingData.bindings[plane1BindingIndex],
+                DAWN_ASSERT(mBindingData.bindings[info.plane1] == nullptr);
+                DAWN_TRY_ASSIGN(mBindingData.bindings[info.plane1],
                                 GetDevice()->GetOrCreatePlaceholderTextureViewForExternalTexture());
 
-                DAWN_ASSERT(mBindingData.bindings[paramsBindingIndex] == nullptr);
+                DAWN_ASSERT(mBindingData.bindings[info.params] == nullptr);
                 Ref<BufferBase> paramsBuffer;
                 DAWN_TRY_ASSIGN(paramsBuffer,
                                 MakeParamsBufferForSimpleView(GetDevice(), entry->textureView));
-                mBindingData.bindings[paramsBindingIndex] = paramsBuffer;
-                mBindingData.bufferData[paramsBindingIndex].offset = 0;
-                mBindingData.bufferData[paramsBindingIndex].size = paramsBuffer->GetSize();
-            }
-            continue;
-        }
+                mBindingData.bindings[info.params] = paramsBuffer;
+                mBindingData.bufferData[info.params].offset = 0;
+                mBindingData.bufferData[info.params].size = paramsBuffer->GetSize();
+                return {};
+            },
 
-        if (entry->sampler != nullptr) {
-            DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = entry->sampler;
-            continue;
-        }
-
-        if (auto* texelBufferBindingEntry = entry.Get<TexelBufferBindingEntry>()) {
-            DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = texelBufferBindingEntry->texelBufferView;
-            continue;
-        }
-
-        // Here we unpack external texture bindings into multiple additional bindings for the
-        // external texture's contents. New binding locations previously determined in the bind
-        // group layout are created in this bind group and filled with the external texture's
-        // underlying resources.
-        if (auto* externalTextureBindingEntry = entry.Get<ExternalTextureBindingEntry>()) {
-            mBoundExternalTextures.push_back(externalTextureBindingEntry->externalTexture);
-
-            ExternalTextureBindingExpansionMap expansions =
-                layout->GetExternalTextureBindingExpansionMap();
-            ExternalTextureBindingExpansionMap::iterator it =
-                expansions.find(BindingNumber(entry->binding));
-
-            DAWN_ASSERT(it != expansions.end());
-
-            BindingIndex plane0BindingIndex = layout->GetBindingIndex(it->second.plane0);
-            BindingIndex plane1BindingIndex = layout->GetBindingIndex(it->second.plane1);
-            BindingIndex paramsBindingIndex = layout->GetBindingIndex(it->second.params);
-
-            DAWN_ASSERT(mBindingData.bindings[plane0BindingIndex] == nullptr);
-
-            mBindingData.bindings[plane0BindingIndex] =
-                externalTextureBindingEntry->externalTexture->GetTextureViews()[0];
-
-            DAWN_ASSERT(mBindingData.bindings[plane1BindingIndex] == nullptr);
-            mBindingData.bindings[plane1BindingIndex] =
-                externalTextureBindingEntry->externalTexture->GetTextureViews()[1];
-
-            DAWN_ASSERT(mBindingData.bindings[paramsBindingIndex] == nullptr);
-            mBindingData.bindings[paramsBindingIndex] =
-                externalTextureBindingEntry->externalTexture->GetParamsBuffer();
-            mBindingData.bufferData[paramsBindingIndex].offset = 0;
-            mBindingData.bufferData[paramsBindingIndex].size =
-                sizeof(dawn::native::ExternalTextureParams);
-
-            continue;
-        }
+            [](const StaticSamplerBindingInfo&) -> MaybeError { DAWN_UNREACHABLE(); }));
     }
 
     ForEachUnverifiedBufferBindingIndexImpl(layout,
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index 8ad772a..e387b60 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -504,6 +504,9 @@
         bindingInfo.bindingLayout = TexelBufferBindingInfo::From(*texelBufferLayout);
     } else if (auto* staticSamplerBindingLayout = binding.Get<StaticSamplerBindingLayout>()) {
         bindingInfo.bindingLayout = StaticSamplerBindingInfo::From(*staticSamplerBindingLayout);
+    } else if (binding.Has<ExternalTextureBindingLayout>()) {
+        // The BindingIndex members are filled later once we know the order of bindings.
+        bindingInfo.bindingLayout = ExternalTextureBindingInfo{};
     } else {
         DAWN_UNREACHABLE();
     }
@@ -511,19 +514,53 @@
     return bindingInfo;
 }
 
+bool SortBindingsCompare(const BindingInfo& a, const BindingInfo& b) {
+    if (&a == &b) {
+        return false;
+    }
+
+    // Buffers with dynamic offsets come first and then the rest of the buffers. Other bindings are
+    // only grouped by types. This is to make it easier and faster to handle them.
+    auto TypeOrder = [](const BindingInfo& info) {
+        return MatchVariant(
+            info.bindingLayout,
+            [&](const BufferBindingInfo& layout) {
+                return layout.hasDynamicOffset ? BindingTypeOrder_DynamicBuffer
+                                               : BindingTypeOrder_RegularBuffer;
+            },
+            [&](const TextureBindingInfo&) { return BindingTypeOrder_SampledTexture; },
+            [&](const StorageTextureBindingInfo&) { return BindingTypeOrder_StorageTexture; },
+            [&](const SamplerBindingInfo&) { return BindingTypeOrder_RegularSampler; },
+            [&](const StaticSamplerBindingInfo&) { return BindingTypeOrder_StaticSampler; },
+            [&](const TexelBufferBindingInfo&) { return BindingTypeOrder_TexelBuffer; },
+            [&](const InputAttachmentBindingInfo&) { return BindingTypeOrder_InputAttachment; },
+            [&](const ExternalTextureBindingInfo&) { return BindingTypeOrder_ExternalTexture; });
+    };
+
+    auto aOrder = TypeOrder(a);
+    auto bOrder = TypeOrder(b);
+    if (aOrder != bOrder) {
+        return aOrder < bOrder;
+    }
+
+    // Afterwards sort the bindings by binding number. This is necessary because dynamic buffers
+    // are applied in order of increasing binding number in SetBindGroup. It also ensures that
+    // bindings for binding arrays stay contiguous as that's required by backends.
+    return a.binding < b.binding;
+}
+
 // This function handles the conversion of the API format for each binding info to Dawn's internal
 // representation of them. This is also where the ExternalTextures are replaced and expanded in the
 // various bindings that are used internally in Dawn. Arrays are also expanded to individual
 // bindings here.
 struct ExpandedBindingInfo {
+    BindGroupLayoutInternalBase::BindingMap apiBindingMap;
     ityp::vector<BindingIndex, BindingInfo> entries;
     ExternalTextureBindingExpansionMap externalTextureBindingExpansions;
-    std::optional<BindingNumber> dynamicArrayMetatada;
+    std::optional<BindingIndex> dynamicArrayMetadata;
 };
 ExpandedBindingInfo ConvertAndExpandBGLEntries(
     const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor) {
-    ExpandedBindingInfo result;
-
     // When new BGL entries are created, we use binding numbers decreasing from the max uint32_t
     // to ensure there are no collisions and that validation will prevent using these BindingNumbers
     // when creating a bindgroup (so there is no risk of applications injecting their own buffer for
@@ -542,34 +579,44 @@
     // ExternalTextures in bind group with dynamic binding arrays.
     BindingNumber nextOpenBindingNumberForNewEntryET = kMaxBindingsPerBindGroupTyped;
     BindingNumber nextOpenBindingNumberForNewEntryNonET = std::numeric_limits<BindingNumber>::max();
+
+    ityp::vector<BindingIndex, BindingInfo> entries;
+    absl::flat_hash_set<BindingNumber> internalEntries;
+
+    // Keep track of the BindingNumbers for additional bindings for external textures, it is used
+    // below to link the ExternalTextureBindingInfo to the BindingIndex of the additional bindings.
+    ExternalTextureBindingExpansionMap externalTextureExpansions;
+
     for (uint32_t i = 0; i < descriptor->entryCount; i++) {
         UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]);
 
-        // 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.
+        // External textures are expanded to add two sampled texture bindings and one uniform buffer
+        // binding. The external texture is still added to the entries to be used in validation and
+        // to know where the additional bindings are located.
         if (entry.Get<ExternalTextureBindingLayout>()) {
             DAWN_ASSERT(entry->bindingArraySize <= 1);
-            dawn::native::ExternalTextureBindingExpansion bindingExpansion;
 
             BindingInfo plane0Entry = CreateSampledTextureBindingForExternalTexture(
-                BindingNumber(entry->binding), entry->visibility);
-            bindingExpansion.plane0 = BindingNumber(plane0Entry.binding);
-            result.entries.push_back(plane0Entry);
+                nextOpenBindingNumberForNewEntryET++, entry->visibility);
+            entries.push_back(plane0Entry);
+            internalEntries.insert(plane0Entry.binding);
 
             BindingInfo plane1Entry = CreateSampledTextureBindingForExternalTexture(
                 nextOpenBindingNumberForNewEntryET++, entry->visibility);
-            bindingExpansion.plane1 = BindingNumber(plane1Entry.binding);
-            result.entries.push_back(plane1Entry);
+            entries.push_back(plane1Entry);
+            internalEntries.insert(plane1Entry.binding);
 
             BindingInfo paramsEntry = CreateUniformBindingForExternalTexture(
                 nextOpenBindingNumberForNewEntryET++, entry->visibility);
-            bindingExpansion.params = BindingNumber(paramsEntry.binding);
-            result.entries.push_back(paramsEntry);
+            entries.push_back(paramsEntry);
+            internalEntries.insert(paramsEntry.binding);
 
-            result.externalTextureBindingExpansions.insert(
-                {BindingNumber(entry->binding), bindingExpansion});
-            continue;
+            externalTextureExpansions.insert({BindingNumber(entry->binding),
+                                              {
+                                                  .plane0 = BindingNumber(plane0Entry.binding),
+                                                  .plane1 = BindingNumber(plane1Entry.binding),
+                                                  .params = BindingNumber(paramsEntry.binding),
+                                              }});
         }
 
         // Add one BindingInfo per element of the array with increasing indexInArray for backends to
@@ -578,15 +625,18 @@
         BindingInfo info = ConvertToBindingInfo(entry);
         for (BindingIndex indexInArray : Range(info.arraySize)) {
             info.indexInArray = indexInArray;
-            result.entries.push_back(info);
+            entries.push_back(info);
             info.binding++;
         }
     }
 
-    // Add an internal entry for the metadata buffer of the dynamic array if needed.
+    // Add an internal entry for the metadata buffer of the dynamic array if needed. Remember the
+    // BindingNumber to convert it to a BindingIndex after sorting of bindings.
+    BindingNumber dynamicArrayMetadataBindingNumber;
     if (descriptor.Has<BindGroupLayoutDynamicBindingArray>()) {
+        dynamicArrayMetadataBindingNumber = nextOpenBindingNumberForNewEntryNonET--;
         BindingInfo metadataEntry = {
-            .binding = nextOpenBindingNumberForNewEntryNonET--,
+            .binding = dynamicArrayMetadataBindingNumber,
             .visibility = kAllStages,
             .bindingLayout = BufferBindingInfo{{
                 .type = wgpu::BufferBindingType::ReadOnlyStorage,
@@ -594,10 +644,50 @@
                 .minBindingSize = 4,
                 .hasDynamicOffset = false,
             }}};
-        result.entries.push_back(metadataEntry);
-        result.dynamicArrayMetatada = metadataEntry.binding;
+        entries.push_back(metadataEntry);
+        internalEntries.insert(dynamicArrayMetadataBindingNumber);
     }
 
+    // Reorder bindings internally and compute the complete BindingNumber->BindingIndex map.
+    std::sort(entries.begin(), entries.end(), SortBindingsCompare);
+
+    absl::flat_hash_map<BindingNumber, BindingIndex> fullBindingMap;
+    for (const auto [i, binding] : Enumerate(entries)) {
+        const auto& [_, inserted] = fullBindingMap.emplace(binding.binding, i);
+        DAWN_ASSERT(inserted);
+    }
+
+    // Store the location of expanded entries in ExternalTexture layouts.
+    for (const auto& [etBindingNumber, expansion] : externalTextureExpansions) {
+        auto& layout = entries[fullBindingMap[etBindingNumber]];
+        layout.bindingLayout = ExternalTextureBindingInfo{{
+            .params = fullBindingMap[expansion.params],
+            .plane0 = fullBindingMap[expansion.plane0],
+            .plane1 = fullBindingMap[expansion.plane1],
+        }};
+    }
+
+    // Now build the result.
+    ExpandedBindingInfo result;
+
+    // Build the user-facing binding map.
+    for (const auto [i, binding] : Enumerate(entries)) {
+        if (internalEntries.contains(binding.binding)) {
+            continue;
+        }
+
+        APIBindingIndex index = APIBindingIndex(uint32_t(i));
+        const auto& [_, inserted] = result.apiBindingMap.emplace(binding.binding, index);
+        DAWN_ASSERT(inserted);
+    }
+
+    // Return the location of the metadata buffer in the reordered bindings.
+    if (descriptor.Has<BindGroupLayoutDynamicBindingArray>()) {
+        result.dynamicArrayMetadata = fullBindingMap[dynamicArrayMetadataBindingNumber];
+    }
+
+    result.entries = std::move(entries);
+    result.externalTextureBindingExpansions = std::move(externalTextureExpansions);
     return result;
 }
 
@@ -632,19 +722,13 @@
     mExternalTextureBindingExpansionMap =
         std::move(unpackedBindings.externalTextureBindingExpansions);
     mBindingInfo = std::move(unpackedBindings.entries);
-
-    // Reorder bindings internally and compute the BindingNumber->BindingIndex map.
-    std::sort(mBindingInfo.begin(), mBindingInfo.end(), SortBindingsCompare);
-    for (const auto [i, binding] : Enumerate(mBindingInfo)) {
-        const auto& [_, inserted] = mBindingMap.emplace(binding.binding, i);
-        DAWN_ASSERT(inserted);
-    }
+    mBindingMap = std::move(unpackedBindings.apiBindingMap);
 
     DAWN_ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()}));
     DAWN_ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped);
 
     // Compute various counts of expanded bindings and other metadata.
-    std::array<BindingIndex, Order_Count + 1> counts{};
+    std::array<BindingIndex, BindingTypeOrder_Count + 1> counts{};
     for (const auto& binding : mBindingInfo) {
         MatchVariant(
             binding.bindingLayout,
@@ -653,7 +737,7 @@
                     mUnverifiedBufferCount++;
                 }
                 if (layout.hasDynamicOffset) {
-                    counts[Order_DynamicBuffer]++;
+                    counts[BindingTypeOrder_DynamicBuffer]++;
                     switch (layout.type) {
                         case wgpu::BufferBindingType::Storage:
                         case kInternalStorageBufferBinding:
@@ -668,21 +752,21 @@
                             break;
                     }
                 } else {
-                    counts[Order_RegularBuffer]++;
+                    counts[BindingTypeOrder_RegularBuffer]++;
                 }
             },
-            [&](const TextureBindingInfo&) { counts[Order_SampledTexture]++; },
-            [&](const StorageTextureBindingInfo&) { counts[Order_StorageTexture]++; },
-            [&](const SamplerBindingInfo&) { counts[Order_RegularSampler]++; },
+            [&](const TextureBindingInfo&) { counts[BindingTypeOrder_SampledTexture]++; },
+            [&](const StorageTextureBindingInfo&) { counts[BindingTypeOrder_StorageTexture]++; },
+            [&](const SamplerBindingInfo&) { counts[BindingTypeOrder_RegularSampler]++; },
             [&](const StaticSamplerBindingInfo& layout) {
-                counts[Order_StaticSampler]++;
+                counts[BindingTypeOrder_StaticSampler]++;
                 if (layout.isUsedForSingleTextureBinding) {
                     mNeedsCrossBindingValidation = true;
                 }
             },
-            [&](const TexelBufferBindingInfo&) { counts[Order_TexelBuffer]++; },
-            [&](const InputAttachmentBindingInfo&) { counts[Order_InputAttachment]++; },
-            [&](const ExternalTextureBindingInfo&) { DAWN_UNREACHABLE(); });
+            [&](const TexelBufferBindingInfo&) { counts[BindingTypeOrder_TexelBuffer]++; },
+            [&](const InputAttachmentBindingInfo&) { counts[BindingTypeOrder_InputAttachment]++; },
+            [&](const ExternalTextureBindingInfo&) { counts[BindingTypeOrder_ExternalTexture]++; });
     }
 
     // Do a prefix sum to store the start offset of each binding type.
@@ -704,47 +788,13 @@
         mHasDynamicArray = true;
         mAPIDynamicArrayStart = BindingNumber(dynamic->dynamicArray.start);
         mDynamicArrayKind = dynamic->dynamicArray.kind;
-        mDynamicArrayMetadataBinding = mBindingMap[unpackedBindings.dynamicArrayMetatada.value()];
+        mDynamicArrayMetadataBinding = unpackedBindings.dynamicArrayMetadata.value();
 
         // Pack the dynamic array to start right after static bindings.
         mDynamicArrayStart = mBindingInfo.size();
     }
 }
 
-// static
-bool BindGroupLayoutInternalBase::SortBindingsCompare(const BindingInfo& a, const BindingInfo& b) {
-    if (&a == &b) {
-        return false;
-    }
-
-    // Buffers with dynamic offsets come first and then the rest of the buffers. Other bindings are
-    // only grouped by types. This is to make it easier and faster to handle them.
-    auto TypeOrder = [](const BindingInfo& info) {
-        return MatchVariant(
-            info.bindingLayout,
-            [&](const BufferBindingInfo& layout) {
-                return layout.hasDynamicOffset ? Order_DynamicBuffer : Order_RegularBuffer;
-            },
-            [&](const TextureBindingInfo&) { return Order_SampledTexture; },
-            [&](const StorageTextureBindingInfo&) { return Order_StorageTexture; },
-            [&](const SamplerBindingInfo&) { return Order_RegularSampler; },
-            [&](const StaticSamplerBindingInfo&) { return Order_StaticSampler; },
-            [&](const TexelBufferBindingInfo&) { return Order_TexelBuffer; },
-            [&](const InputAttachmentBindingInfo&) { return Order_InputAttachment; },
-            [&](const ExternalTextureBindingInfo&) -> BindingTypeOrder { DAWN_UNREACHABLE(); });
-    };
-
-    auto aOrder = TypeOrder(a);
-    auto bOrder = TypeOrder(b);
-    if (aOrder != bOrder) {
-        return aOrder < bOrder;
-    }
-
-    // Afterwards sort the bindings by binding number. This is necessary because dynamic buffers
-    // are applied in order of increasing binding number in SetBindGroup.
-    return a.binding < b.binding;
-}
-
 BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
     DeviceBase* device,
     const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor)
@@ -769,22 +819,47 @@
 
 const BindingInfo& BindGroupLayoutInternalBase::GetBindingInfo(BindingIndex bindingIndex) const {
     DAWN_ASSERT(!IsError());
-    DAWN_ASSERT(bindingIndex < mBindingInfo.size());
+    // Assert that this is an internal binding.
+    DAWN_ASSERT(bindingIndex < GetBindingCount());
     return mBindingInfo[bindingIndex];
 }
 
+const BindingInfo& BindGroupLayoutInternalBase::GetAPIBindingInfo(
+    APIBindingIndex bindingIndex) const {
+    DAWN_ASSERT(!IsError());
+    BindingIndex index = BindingIndex(uint32_t(bindingIndex));
+    DAWN_ASSERT(index < mBindingInfo.size());
+    // Assert this is a user-facing binding and not an private internal binding.
+    DAWN_ASSERT(mBindingMap.contains(mBindingInfo[index].binding));
+    return mBindingInfo[index];
+}
+
 const BindGroupLayoutInternalBase::BindingMap& BindGroupLayoutInternalBase::GetBindingMap() const {
     DAWN_ASSERT(!IsError());
     return mBindingMap;
 }
 
 BindingIndex BindGroupLayoutInternalBase::GetBindingIndex(BindingNumber bindingNumber) const {
+    return AsBindingIndex(GetAPIBindingIndex(bindingNumber));
+}
+
+APIBindingIndex BindGroupLayoutInternalBase::GetAPIBindingIndex(BindingNumber bindingNumber) const {
     DAWN_ASSERT(!IsError());
     const auto& it = mBindingMap.find(bindingNumber);
     DAWN_ASSERT(it != mBindingMap.end());
     return it->second;
 }
 
+BindingIndex BindGroupLayoutInternalBase::AsBindingIndex(APIBindingIndex bindingIndex) const {
+    DAWN_ASSERT(!IsError());
+    // Assert this is a user-facing binding and not a private internal binding, and that it
+    // represents an internal bindings.
+    BindingIndex index = BindingIndex(uint32_t(bindingIndex));
+    DAWN_ASSERT(index < GetBindingCount());
+    DAWN_ASSERT(mBindingMap.contains(mBindingInfo[index].binding));
+    return index;
+}
+
 bool BindGroupLayoutInternalBase::HasDynamicArray() const {
     DAWN_ASSERT(!IsError());
     return mHasDynamicArray;
@@ -831,8 +906,9 @@
     // will still record the same.
     for (const auto [id, index] : mBindingMap) {
         recorder.Record(id, index);
+    }
 
-        const BindingInfo& info = mBindingInfo[index];
+    for (const auto& info : mBindingInfo) {
         recorder.Record(info.visibility);
         recorder.Record(info.arraySize);
         recorder.Record(info.indexInArray);
@@ -863,7 +939,10 @@
             [&](const InputAttachmentBindingInfo& layout) {
                 recorder.Record(BindingInfoType::InputAttachment, layout.sampleType);
             },
-            [&](const ExternalTextureBindingInfo& layout) { DAWN_UNREACHABLE(); });
+            [&](const ExternalTextureBindingInfo& layout) {
+                recorder.Record(BindingInfoType::ExternalTexture, layout.params, layout.plane0,
+                                layout.plane1);
+            });
     }
 
     recorder.Record(mHasDynamicArray, mAPIDynamicArrayStart, mDynamicArrayKind);
@@ -901,12 +980,13 @@
 
 BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const {
     DAWN_ASSERT(!IsError());
-    return mBindingInfo.size();
+    return GetBindingTypeStart(BindingTypeOrder_ExternalTexture);
 }
 
 BindingIndex BindGroupLayoutInternalBase::GetDynamicBufferCount() const {
     DAWN_ASSERT(!IsError());
-    return GetBindingTypeEnd(Order_DynamicBuffer) - GetBindingTypeStart(Order_DynamicBuffer);
+    return GetBindingTypeEnd(BindingTypeOrder_DynamicBuffer) -
+           GetBindingTypeStart(BindingTypeOrder_DynamicBuffer);
 }
 
 uint32_t BindGroupLayoutInternalBase::GetDynamicStorageBufferCount() const {
@@ -921,8 +1001,8 @@
 
 uint32_t BindGroupLayoutInternalBase::GetStaticSamplerCount() const {
     DAWN_ASSERT(!IsError());
-    return uint32_t(GetBindingTypeEnd(Order_StaticSampler) -
-                    GetBindingTypeStart(Order_StaticSampler));
+    return uint32_t(GetBindingTypeEnd(BindingTypeOrder_StaticSampler) -
+                    GetBindingTypeStart(BindingTypeOrder_StaticSampler));
 }
 
 const BindingCounts& BindGroupLayoutInternalBase::GetValidationBindingCounts() const {
@@ -931,44 +1011,48 @@
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetDynamicBufferIndices() const {
-    return Range(GetBindingTypeStart(Order_DynamicBuffer), GetBindingTypeEnd(Order_DynamicBuffer));
+    return Range(GetBindingTypeStart(BindingTypeOrder_DynamicBuffer),
+                 GetBindingTypeEnd(BindingTypeOrder_DynamicBuffer));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetBufferIndices() const {
-    return Range(GetBindingTypeStart(Order_DynamicBuffer), GetBindingTypeEnd(Order_RegularBuffer));
+    return Range(GetBindingTypeStart(BindingTypeOrder_DynamicBuffer),
+                 GetBindingTypeEnd(BindingTypeOrder_RegularBuffer));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetStorageTextureIndices() const {
-    return Range(GetBindingTypeStart(Order_StorageTexture),
-                 GetBindingTypeEnd(Order_StorageTexture));
+    return Range(GetBindingTypeStart(BindingTypeOrder_StorageTexture),
+                 GetBindingTypeEnd(BindingTypeOrder_StorageTexture));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetTexelBufferIndices() const {
-    return Range(GetBindingTypeStart(Order_TexelBuffer), GetBindingTypeEnd(Order_TexelBuffer));
+    return Range(GetBindingTypeStart(BindingTypeOrder_TexelBuffer),
+                 GetBindingTypeEnd(BindingTypeOrder_TexelBuffer));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetSampledTextureIndices() const {
-    return Range(GetBindingTypeStart(Order_SampledTexture),
-                 GetBindingTypeEnd(Order_SampledTexture));
+    return Range(GetBindingTypeStart(BindingTypeOrder_SampledTexture),
+                 GetBindingTypeEnd(BindingTypeOrder_SampledTexture));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetTextureIndices() const {
-    return Range(GetBindingTypeStart(Order_SampledTexture),
-                 GetBindingTypeEnd(Order_InputAttachment));
+    return Range(GetBindingTypeStart(BindingTypeOrder_SampledTexture),
+                 GetBindingTypeEnd(BindingTypeOrder_InputAttachment));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetSamplerIndices() const {
-    return Range(GetBindingTypeStart(Order_StaticSampler), GetBindingTypeEnd(Order_RegularSampler));
+    return Range(GetBindingTypeStart(BindingTypeOrder_StaticSampler),
+                 GetBindingTypeEnd(BindingTypeOrder_RegularSampler));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetNonStaticSamplerIndices() const {
-    return Range(GetBindingTypeStart(Order_RegularSampler),
-                 GetBindingTypeEnd(Order_RegularSampler));
+    return Range(GetBindingTypeStart(BindingTypeOrder_RegularSampler),
+                 GetBindingTypeEnd(BindingTypeOrder_RegularSampler));
 }
 
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetInputAttachmentIndices() const {
-    return Range(GetBindingTypeStart(Order_InputAttachment),
-                 GetBindingTypeEnd(Order_InputAttachment));
+    return Range(GetBindingTypeStart(BindingTypeOrder_InputAttachment),
+                 GetBindingTypeEnd(BindingTypeOrder_InputAttachment));
 }
 
 const ExternalTextureBindingExpansionMap&
@@ -994,7 +1078,7 @@
     // Followed by:
     // |---------buffer size array--------|
     // |-uint64_t[mUnverifiedBufferCount]-|
-    const size_t bufferCount = size_t(GetBindingTypeEnd(Order_RegularBuffer));
+    const size_t bufferCount = size_t(GetBindingTypeEnd(BindingTypeOrder_RegularBuffer));
     const size_t bindingCount = size_t(mBindingInfo.size());
 
     size_t objectPointerStart = bufferCount * sizeof(BufferBindingData);
@@ -1007,7 +1091,7 @@
 
 BindGroupLayoutInternalBase::BindingDataPointers
 BindGroupLayoutInternalBase::ComputeBindingDataPointers(void* dataStart) const {
-    const size_t bufferCount = size_t(GetBindingTypeEnd(Order_RegularBuffer));
+    const size_t bufferCount = size_t(GetBindingTypeEnd(BindingTypeOrder_RegularBuffer));
     const size_t bindingCount = size_t(mBindingInfo.size());
 
     BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart);
@@ -1019,7 +1103,7 @@
     DAWN_ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>)));
     DAWN_ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t)));
 
-    return {{bufferData, GetBindingTypeEnd(Order_RegularBuffer)},
+    return {{bufferData, GetBindingTypeEnd(BindingTypeOrder_RegularBuffer)},
             {bindings, GetBindingCount()},
             {unverifiedBufferSizes, mUnverifiedBufferCount}};
 }
@@ -1045,7 +1129,7 @@
     std::string sep = "";
     const BindGroupLayoutInternalBase::BindingMap& bindingMap = GetBindingMap();
     for (const auto [bindingNumber, bindingIndex] : bindingMap) {
-        const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
+        const BindingInfo& bindingInfo = GetAPIBindingInfo(bindingIndex);
         entries += absl::StrFormat("%s%s", sep, bindingInfo);
         sep = ", ";
     }
diff --git a/src/dawn/native/BindGroupLayoutInternal.h b/src/dawn/native/BindGroupLayoutInternal.h
index 496b6df..be34e25 100644
--- a/src/dawn/native/BindGroupLayoutInternal.h
+++ b/src/dawn/native/BindGroupLayoutInternal.h
@@ -50,13 +50,14 @@
 #include "dawn/native/dawn_platform.h"
 
 namespace dawn::native {
-// TODO(dawn:1082): Minor optimization to use BindingIndex instead of BindingNumber
+// TODO(https://crbug.com/42240282): The expansion information is now stored in
+// ExternalTextureBindingInfo. Remove this structure and the map once backends transition to using
+// ExternalTextureBindingInfo.
 struct ExternalTextureBindingExpansion {
     BindingNumber plane0;
     BindingNumber plane1;
     BindingNumber params;
 };
-
 using ExternalTextureBindingExpansionMap =
     absl::flat_hash_map<BindingNumber, ExternalTextureBindingExpansion>;
 
@@ -65,9 +66,58 @@
     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.
+// In the BindGroupLayout, entries are sorted by type for more efficient lookup and iteration.
+// This enum is the order that's used and can also be used to index various ranges of entries.
+// The enum is public so that helper function can use it during creation of the BindGroupLayout,
+// but the order is not meant to be used anywhere else. Use the accessors on the BindGroupLayout for
+// logic that relies on the packing or the order.
+enum BindingTypeOrder : uint32_t {
+    // Buffers
+    BindingTypeOrder_DynamicBuffer,
+    BindingTypeOrder_RegularBuffer,
+    // Textures
+    BindingTypeOrder_SampledTexture,
+    BindingTypeOrder_StorageTexture,
+    BindingTypeOrder_InputAttachment,
+    // Samplers
+    BindingTypeOrder_StaticSampler,
+    BindingTypeOrder_RegularSampler,
+    // Texel Buffers
+    BindingTypeOrder_TexelBuffer,
+    // Start of entries that are expanded in the frontend and aren't actually stored in the bind
+    // groups.
+    BindingTypeOrder_ExternalTexture,
+    BindingTypeOrder_Count,
+};
+
+// BindGroupLayout stores the information passed in the BindGroupLayoutDescriptor but processes it
+// in various ways to make it more efficient to use internally and to add internal bindings used to
+// implement WebGPU feature that don't exist in backend APIs.
+//
+// Storing information in hashmap<BindingNumber, T> would be inefficient because these numbers may
+// be sparse. Instead the are compacted into vectors, and reordered using |BindingTypeOrder| to make
+// it efficient to iterate over all the bindings of a same kind. Indexing the packed bindings is
+// done with |BindingIndex| or |APIBindingIndex| (see below for the explanation).
+//
+// In some cases bindings need to be expanded because a single BGLEntry can match multiple
+// BGEntries when bindingArraySize > 1. To make handling more regular, a fake BGLEntry is created
+// for each array element such that most code doesn't need to be aware of bindingArraySize.
+//
+// We also need to have private bindings that cannot be set by the users: dynamic binding array or
+// ExternalTextures add additional bindings for their inner workings which are private to Dawn.
+// Conversely ExternalTexture is a pure frontend object and doesn't exist in backends, so
+// dawn::native must mostly be unaware about it. This is where |BindingIndex| and |APIBindingIndex|
+// are different:
+//
+//  - |APIBindingIndex| are user-facing bindings and cannot be used to access private bindings. It
+//  is used in code for BindGroup validation and opertations and when reflecting/validating WGSL
+//  bind points.
+//  - |BindingIndex| are Dawn-facing bindings where ExternalTexture shouldn't be accessed and
+//  instead can be used to access internal bindings.
+//
+// Internally both |APIBindingIndex| and |BindingIndex| are used to access the same vector, but the
+// types are used to force uses of different BindGroupLayout accessors that ASSERT the invariant
+// above.
 class BindGroupLayoutInternalBase : public ApiObjectBase,
                                     public CachedObject,
                                     public ContentLessObjectCacheable<BindGroupLayoutInternalBase> {
@@ -81,14 +131,21 @@
 
     ObjectType GetType() const override;
 
-    // A map from the BindingNumber to its packed BindingIndex.
-    using BindingMap = std::map<BindingNumber, BindingIndex>;
+    // A map from the BindingNumber to its packed APIBindingIndex.
+    using BindingMap = std::map<BindingNumber, APIBindingIndex>;
 
     // Getters for static bindings
     const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const;
+    const BindingInfo& GetAPIBindingInfo(APIBindingIndex bindingIndex) const;
     const BindingMap& GetBindingMap() const;
+    BindingIndex AsBindingIndex(APIBindingIndex index) const;
+    // TODO(https://crbug.com/42240282): BindingNumbers are always user-facing and can only
+    // represent APIBindingIndex. Remove this getter once backends don't need to use the
+    // ExternalTextureBindingExpansionMap anymore.
     BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
+    APIBindingIndex GetAPIBindingIndex(BindingNumber bindingNumber) const;
 
+    // Returns the number of internal bindings, excluding things like ExternalTexture.
     BindingIndex GetBindingCount() const;
     // Returns |BindingIndex| because dynamic buffers are packed at the front.
     BindingIndex GetDynamicBufferCount() const;
@@ -131,6 +188,8 @@
     const BindingCounts& GetValidationBindingCounts() const;
 
     // Used to specify unpacked external texture binding slots when transforming shader modules.
+    // TODO(https://crbug.com/42240282): The expansion information is now stored in
+    // ExternalTextureBindingInfo. Remove this getter once all the backends are updated to use it.
     const ExternalTextureBindingExpansionMap& GetExternalTextureBindingExpansionMap() const;
 
     uint32_t GetUnexpandedBindingCount() const;
@@ -183,38 +242,19 @@
     // The entries with arbitrary BindingNumber are repacked into a compact BindingIndex range.
     ityp::vector<BindingIndex, BindingInfo> mBindingInfo;
 
-    // When they are packed, the entries are also sorted by type for more efficient lookup and
-    // iteration. This enum is the order that's used and can also be used to index various ranges of
-    // entries.
-    enum BindingTypeOrder : uint32_t {
-        // Buffers
-        Order_DynamicBuffer,
-        Order_RegularBuffer,
-        // Textures
-        Order_SampledTexture,
-        Order_StorageTexture,
-        Order_InputAttachment,
-        // Samplers
-        Order_StaticSampler,
-        Order_RegularSampler,
-        // Texel Buffers
-        Order_TexelBuffer,
-        Order_Count,
-    };
-    static bool SortBindingsCompare(const BindingInfo& a, const BindingInfo& b);
-
     // Keep a list of the start indices for each kind of binding. Then (exclusive) end of a range
     // of bindings is the start of the next range. (that's why we use count + 1 entry, to have the
     // "end" of the last binding type)
     BindingIndex GetBindingTypeStart(BindingTypeOrder type) const;
     BindingIndex GetBindingTypeEnd(BindingTypeOrder type) const;
-    std::array<BindingIndex, Order_Count + 1> mBindingTypeStart;
+    std::array<BindingIndex, BindingTypeOrder_Count + 1> mBindingTypeStart;
 
     // Additional counts for types of bindings.
     uint32_t mUnverifiedBufferCount = 0;
     uint32_t mDynamicStorageBufferCount = 0;
 
     // Map from BindGroupLayoutEntry.binding as BindingNumber to packed indices as BindingIndex.
+    // TODO(https://issues.chromium.org/448578977): Use a more optimized map type.
     BindingMap mBindingMap;
     // Map from the BindingNumber of the ExternalTexture to the BindingNumber of the expansion.
     ExternalTextureBindingExpansionMap mExternalTextureBindingExpansionMap;
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h
index 957bb78..9c74bb3 100644
--- a/src/dawn/native/BindingInfo.h
+++ b/src/dawn/native/BindingInfo.h
@@ -122,8 +122,11 @@
 };
 #undef SAMPLER_BINDING_INFO_MEMBER
 
-// A mirror of wgpu::ExternalTextureBindingLayout for use inside dawn::native.
-#define EXTERNAL_TEXTURE_BINDING_INFO_MEMBER(X)  // ExternalTextureBindingInfo has no member
+// The binding layout for ExternalTexture contains the indices of the expanded entries for it.
+#define EXTERNAL_TEXTURE_BINDING_INFO_MEMBER(X) \
+    X(BindingIndex, params)                     \
+    X(BindingIndex, plane0)                     \
+    X(BindingIndex, plane1)
 DAWN_SERIALIZABLE(struct, ExternalTextureBindingInfo, EXTERNAL_TEXTURE_BINDING_INFO_MEMBER){};
 #undef EXTERNAL_TEXTURE_BINDING_INFO_MEMBER
 
diff --git a/src/dawn/native/IntegerTypes.h b/src/dawn/native/IntegerTypes.h
index df8a6c5..6a03c2a 100644
--- a/src/dawn/native/IntegerTypes.h
+++ b/src/dawn/native/IntegerTypes.h
@@ -55,7 +55,10 @@
 using BindingNumber = TypedInteger<struct BindingNumberT, uint32_t>;
 constexpr BindingNumber kMaxBindingsPerBindGroupTyped = BindingNumber(kMaxBindingsPerBindGroup);
 
-// Binding numbers get mapped to a packed range of indices
+// Binding numbers get mapped to a packed range of indices. APIBindingIndex is for user-facing
+// indices in the packed range, while BindingIndex is Dawn-facing. See comment for the
+// BindGroupLayoutInternal class for more details.
+using APIBindingIndex = TypedInteger<struct APIBindingIndexT, uint32_t>;
 using BindingIndex = TypedInteger<struct BindingIndexT, uint32_t>;
 
 // Bind group indinces represent the index in the SetBindGroup, the index in
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 0504202..7a03c74 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -624,8 +624,8 @@
     const auto& bindingIt = layoutBindings.find(bindingNumber);
     DAWN_INVALID_IF(bindingIt == layoutBindings.end(), "Binding doesn't exist in %s.", layout);
 
-    BindingIndex bindingIndex(bindingIt->second);
-    const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex);
+    APIBindingIndex bindingIndex(bindingIt->second);
+    const BindingInfo& layoutInfo = layout->GetAPIBindingInfo(bindingIndex);
 
     BindingInfoType bindingLayoutType = GetBindingInfoType(layoutInfo);
     BindingInfoType shaderBindingType = GetShaderBindingType(shaderInfo);
@@ -1645,37 +1645,47 @@
         if (pair.sampler == EntryPointMetadata::nonSamplerBindingPoint) {
             continue;
         }
-        const BindGroupLayoutInternalBase* samplerBGL =
-            layout->GetBindGroupLayout(pair.sampler.group);
-        const BindingInfo& samplerInfo =
-            samplerBGL->GetBindingInfo(samplerBGL->GetBindingIndex(pair.sampler.binding));
+
         bool samplerIsFiltering = false;
-        if (std::holds_alternative<StaticSamplerBindingInfo>(samplerInfo.bindingLayout)) {
-            const StaticSamplerBindingInfo& samplerLayout =
-                std::get<StaticSamplerBindingInfo>(samplerInfo.bindingLayout);
-            samplerIsFiltering = samplerLayout.sampler->IsFiltering();
-        } else {
-            const SamplerBindingInfo& samplerLayout =
-                std::get<SamplerBindingInfo>(samplerInfo.bindingLayout);
-            samplerIsFiltering = (samplerLayout.type == wgpu::SamplerBindingType::Filtering);
+        {
+            const BindGroupLayoutInternalBase* samplerBGL =
+                layout->GetBindGroupLayout(pair.sampler.group);
+            const BindingInfo& samplerInfo =
+                samplerBGL->GetAPIBindingInfo(samplerBGL->GetAPIBindingIndex(pair.sampler.binding));
+            if (std::holds_alternative<StaticSamplerBindingInfo>(samplerInfo.bindingLayout)) {
+                const StaticSamplerBindingInfo& samplerLayout =
+                    std::get<StaticSamplerBindingInfo>(samplerInfo.bindingLayout);
+                samplerIsFiltering = samplerLayout.sampler->IsFiltering();
+            } else {
+                const SamplerBindingInfo& samplerLayout =
+                    std::get<SamplerBindingInfo>(samplerInfo.bindingLayout);
+                samplerIsFiltering = (samplerLayout.type == wgpu::SamplerBindingType::Filtering);
+            }
         }
-        if (!samplerIsFiltering) {
-            continue;
+
+        wgpu::TextureSampleType sampleType = wgpu::TextureSampleType::Undefined;
+        {
+            const BindGroupLayoutInternalBase* textureBGL =
+                layout->GetBindGroupLayout(pair.texture.group);
+            const BindingInfo& textureInfo =
+                textureBGL->GetAPIBindingInfo(textureBGL->GetAPIBindingIndex(pair.texture.binding));
+
+            if (std::holds_alternative<ExternalTextureBindingInfo>(textureInfo.bindingLayout)) {
+                sampleType = wgpu::TextureSampleType::Float;
+            } else {
+                const TextureBindingInfo& sampledTextureBindingInfo =
+                    std::get<TextureBindingInfo>(textureInfo.bindingLayout);
+                sampleType = sampledTextureBindingInfo.sampleType;
+            }
         }
-        const BindGroupLayoutInternalBase* textureBGL =
-            layout->GetBindGroupLayout(pair.texture.group);
-        const BindingInfo& textureInfo =
-            textureBGL->GetBindingInfo(textureBGL->GetBindingIndex(pair.texture.binding));
-        const TextureBindingInfo& sampledTextureBindingInfo =
-            std::get<TextureBindingInfo>(textureInfo.bindingLayout);
 
         DAWN_INVALID_IF(
-            sampledTextureBindingInfo.sampleType != wgpu::TextureSampleType::Float &&
-                sampledTextureBindingInfo.sampleType != kInternalResolveAttachmentSampleType,
+            samplerIsFiltering && sampleType != wgpu::TextureSampleType::Float &&
+                sampleType != kInternalResolveAttachmentSampleType,
             "Texture binding (group:%u, binding:%u) is %s but used statically with a sampler "
             "(group:%u, binding:%u) that's %s",
-            pair.texture.group, pair.texture.binding, sampledTextureBindingInfo.sampleType,
-            pair.sampler.group, pair.sampler.binding, wgpu::SamplerBindingType::Filtering);
+            pair.texture.group, pair.texture.binding, sampleType, pair.sampler.group,
+            pair.sampler.binding, wgpu::SamplerBindingType::Filtering);
     }
 
     // Validate compatibility of the pixel local storage.
diff --git a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
index 0caf74d..3fee12a 100644
--- a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
+++ b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
@@ -80,8 +80,6 @@
     TRACE_EVENT0(device->GetPlatform(), General, "ShaderModuleD3D11::Compile");
     DAWN_ASSERT(!IsError());
 
-    const EntryPointMetadata& entryPoint = GetEntryPoint(programmableStage.entryPoint);
-
     d3d::D3DCompilationRequest req = {};
     req.tracePlatform = UnsafeUnserializedValue(device->GetPlatform());
     req.hlsl.shaderModel = 50;
@@ -110,25 +108,33 @@
             break;
     }
 
-    const BindingInfoArray& moduleBindingInfo = entryPoint.bindings;
-
     tint::Bindings bindings;
 
     for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
         const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
         const auto& indices = layout->GetBindingTableIndexMap()[group];
-        const BindingGroupInfoMap& moduleGroupBindingInfo = moduleBindingInfo[group];
 
-        for (const auto& [binding, shaderBindingInfo] : moduleGroupBindingInfo) {
-            BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
-            tint::BindingPoint srcBindingPoint{static_cast<uint32_t>(group),
-                                               static_cast<uint32_t>(binding)};
-            tint::BindingPoint dstBindingPoint{0u, indices[bindingIndex][stage]};
-            DAWN_ASSERT(dstBindingPoint.binding != PipelineLayout::kInvalidSlot);
+        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
+            if (!(bgl->GetAPIBindingInfo(apiBindingIndex).visibility & StageBit(stage))) {
+                continue;
+            }
+
+            tint::BindingPoint srcBindingPoint{
+                .group = uint32_t(group),
+                .binding = uint32_t(bindingNumber),
+            };
+
+            auto ComputeDestinationBindingPoint = [&](BindingIndex bindingIndex) {
+                tint::BindingPoint dstBindingPoint{0u, indices[bindingIndex][stage]};
+                DAWN_ASSERT(dstBindingPoint.binding != PipelineLayout::kInvalidSlot);
+                return dstBindingPoint;
+            };
 
             MatchVariant(
-                shaderBindingInfo.bindingInfo,
+                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                 [&](const BufferBindingInfo& bindingInfo) {
+                    tint::BindingPoint dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     switch (bindingInfo.type) {
                         case wgpu::BufferBindingType::Uniform:
                             bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
@@ -146,35 +152,35 @@
                     }
                 },
                 [&](const SamplerBindingInfo& bindingInfo) {
-                    bindings.sampler.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TextureBindingInfo& bindingInfo) {
-                    bindings.texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const StorageTextureBindingInfo& bindingInfo) {
-                    bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.storage_texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TexelBufferBindingInfo& bindingInfo) {
                     // TODO(crbug/382544164): Prototype texel buffer feature
                     DAWN_UNREACHABLE();
                 },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
-                    const auto& bindingMap = bgl->GetExternalTextureBindingExpansionMap();
-                    const auto& expansion = bindingMap.find(binding);
-                    DAWN_ASSERT(expansion != bindingMap.end());
-
-                    const auto& bindingExpansion = expansion->second;
-                    tint::BindingPoint plane0{
-                        0u, indices[bgl->GetBindingIndex(bindingExpansion.plane0)][stage]};
-                    tint::BindingPoint plane1{
-                        0u, indices[bgl->GetBindingIndex(bindingExpansion.plane1)][stage]};
-                    tint::BindingPoint metadata{
-                        0u, indices[bgl->GetBindingIndex(bindingExpansion.params)][stage]};
                     bindings.external_texture.emplace(
-                        srcBindingPoint, tint::ExternalTexture{metadata, plane0, plane1});
+                        srcBindingPoint,
+                        tint::ExternalTexture{
+                            .metadata = ComputeDestinationBindingPoint(bindingInfo.params),
+                            .plane0 = ComputeDestinationBindingPoint(bindingInfo.plane0),
+                            .plane1 = ComputeDestinationBindingPoint(bindingInfo.plane1)});
                 },
 
-                [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
+                [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); },
+                [](const StaticSamplerBindingInfo&) { DAWN_UNREACHABLE(); });
         }
     }
 
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
index 43f0c05..8999ab4 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
@@ -167,26 +167,29 @@
     tint::Bindings bindings;
     std::vector<BindingPoint> ignored_by_robustness;
 
-    const BindingInfoArray& moduleBindingInfo = entryPoint.bindings;
     for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
         const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
-        const BindingGroupInfoMap& moduleGroupBindingInfo = moduleBindingInfo[group];
 
-        for (const auto& [binding, shaderBindingInfo] : moduleGroupBindingInfo) {
-            BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
-            BindingPoint srcBindingPoint{static_cast<uint32_t>(group),
-                                         static_cast<uint32_t>(binding)};
+        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
+            tint::BindingPoint srcBindingPoint{
+                .group = uint32_t(group),
+                .binding = uint32_t(bindingNumber),
+            };
 
             // Remap the WGSL bindings to the register numbers computed in the
             // d3d12::BindGroupLayout that packs them per register type. The group decoration stays
             // the same as HLSL supports register spaces that are a similar concept of a second
             // dimension of binding indices.
-            BindingPoint dstBindingPoint{static_cast<uint32_t>(group),
-                                         bgl->GetShaderRegister(bindingIndex)};
+            auto ComputeDestinationBindingPoint = [&](BindingIndex bindingIndex) {
+                return tint::BindingPoint{.group = uint32_t(group),
+                                          .binding = bgl->GetShaderRegister(bindingIndex)};
+            };
 
             MatchVariant(
-                shaderBindingInfo.bindingInfo,
+                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                 [&](const BufferBindingInfo& bindingInfo) {
+                    tint::BindingPoint dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     switch (bindingInfo.type) {
                         case wgpu::BufferBindingType::Uniform:
                             bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
@@ -203,37 +206,38 @@
                             break;
                     }
                 },
+
                 [&](const SamplerBindingInfo& bindingInfo) {
-                    bindings.sampler.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
+                },
+                [&](const StaticSamplerBindingInfo& bindingInfo) {
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TextureBindingInfo& bindingInfo) {
-                    bindings.texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const StorageTextureBindingInfo& bindingInfo) {
-                    bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.storage_texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TexelBufferBindingInfo& bindingInfo) {
                     // TODO(crbug/382544164): Prototype texel buffer feature
                     DAWN_UNREACHABLE();
                 },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
-                    const auto& bindingMap = bgl->GetExternalTextureBindingExpansionMap();
-                    const auto& expansion = bindingMap.find(binding);
-                    DAWN_ASSERT(expansion != bindingMap.end());
-
-                    const auto& bindingExpansion = expansion->second;
-                    tint::BindingPoint plane0{
-                        static_cast<uint32_t>(group),
-                        bgl->GetShaderRegister(bgl->GetBindingIndex(bindingExpansion.plane0))};
-                    tint::BindingPoint plane1{
-                        static_cast<uint32_t>(group),
-                        bgl->GetShaderRegister(bgl->GetBindingIndex(bindingExpansion.plane1))};
-                    tint::BindingPoint metadata{
-                        static_cast<uint32_t>(group),
-                        bgl->GetShaderRegister(bgl->GetBindingIndex(bindingExpansion.params))};
-
                     bindings.external_texture.emplace(
-                        srcBindingPoint, tint::ExternalTexture{metadata, plane0, plane1});
+                        srcBindingPoint,
+                        tint::ExternalTexture{
+                            .metadata = ComputeDestinationBindingPoint(bindingInfo.params),
+                            .plane0 = ComputeDestinationBindingPoint(bindingInfo.plane0),
+                            .plane1 = ComputeDestinationBindingPoint(bindingInfo.plane1)});
                 },
 
                 [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index 92463a8..485eac5 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -865,7 +865,7 @@
                     DAWN_CHECK(false);
                 },
                 [](const InputAttachmentBindingInfo&) { DAWN_CHECK(false); },
-                [](const ExternalTextureBindingInfo&) { DAWN_UNREACHABLE(); });
+                [](const ExternalTextureBindingInfo&) { DAWN_CHECK(false); });
         }
 
         uint32_t offset_size = uint32_t(dynamicOffsets.size());
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index 8a52061..a1e4478 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -132,29 +132,27 @@
     for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
         const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
 
-        for (const auto& currentModuleBindingInfo : moduleBindingInfo[group]) {
-            // We cannot use structured binding here because lambda expressions can only capture
-            // variables, while structured binding doesn't introduce variables.
-            const auto& binding = currentModuleBindingInfo.first;
-            const auto& shaderBindingInfo = currentModuleBindingInfo.second;
-
+        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
             tint::BindingPoint srcBindingPoint{
                 .group = uint32_t(group),
-                .binding = uint32_t(binding),
+                .binding = uint32_t(bindingNumber),
             };
 
-            BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
             auto& bindingIndexInfo = layout->GetBindingIndexInfo(stage)[group];
-            uint32_t shaderIndex = bindingIndexInfo[bindingIndex];
 
-            tint::BindingPoint dstBindingPoint{
-                .group = useArgumentBuffers ? uint32_t(group) : 0,
-                .binding = shaderIndex,
+            auto ComputeDestinationBindingPoint = [&](BindingIndex bindingIndex) {
+                uint32_t shaderIndex = bindingIndexInfo[bindingIndex];
+                return tint::BindingPoint{
+                    .group = useArgumentBuffers ? uint32_t(group) : 0,
+                    .binding = shaderIndex,
+                };
             };
 
             MatchVariant(
-                shaderBindingInfo.bindingInfo,
+                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                 [&](const BufferBindingInfo& bindingInfo) {
+                    tint::BindingPoint dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     switch (bindingInfo.type) {
                         case wgpu::BufferBindingType::Uniform:
                             bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
@@ -172,13 +170,19 @@
                     }
                 },
                 [&](const SamplerBindingInfo& bindingInfo) {
-                    bindings.sampler.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TextureBindingInfo& bindingInfo) {
-                    bindings.texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const StorageTextureBindingInfo& bindingInfo) {
-                    bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.storage_texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TexelBufferBindingInfo& bindingInfo) {
                     // Metal does not support texel buffers.
@@ -186,27 +190,14 @@
                     DAWN_UNREACHABLE();
                 },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
-                    const auto& etBindingMap = bgl->GetExternalTextureBindingExpansionMap();
-                    const auto& expansion = etBindingMap.find(binding);
-                    DAWN_ASSERT(expansion != etBindingMap.end());
-
-                    const auto& bindingExpansion = expansion->second;
-                    tint::BindingPoint plane0{
-                        .group = dstBindingPoint.group,
-                        .binding = bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.plane0)],
-                    };
-                    tint::BindingPoint plane1{
-                        .group = dstBindingPoint.group,
-                        .binding = bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.plane1)],
-                    };
-                    tint::BindingPoint metadata{
-                        .group = dstBindingPoint.group,
-                        .binding = bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.params)],
-                    };
-
                     bindings.external_texture.emplace(
-                        srcBindingPoint, tint::ExternalTexture{metadata, plane0, plane1});
+                        srcBindingPoint,
+                        tint::ExternalTexture{
+                            .metadata = ComputeDestinationBindingPoint(bindingInfo.params),
+                            .plane0 = ComputeDestinationBindingPoint(bindingInfo.plane0),
+                            .plane1 = ComputeDestinationBindingPoint(bindingInfo.plane1)});
                 },
+                [](const StaticSamplerBindingInfo&) { DAWN_UNREACHABLE(); },
                 [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
         }
 
diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp
index 01e3868..77faf7a 100644
--- a/src/dawn/native/opengl/PipelineGL.cpp
+++ b/src/dawn/native/opengl/PipelineGL.cpp
@@ -124,12 +124,8 @@
         // all in this vector.
         absl::InlinedVector<GLint, 1> uniformsToSet;
 
-        const BindGroupLayoutInternalBase* textureBgl =
-            layout->GetBindGroupLayout(combined.textureLocation.group);
-        BindingIndex textureArrayStart =
-            textureBgl->GetBindingIndex(combined.textureLocation.binding);
-
-        for (auto textureArrayElement : Range(combined.textureLocation.arraySize)) {
+        BindingIndex textureArrayStart = combined.textureLocation.index;
+        for (auto textureArrayElement : Range(combined.textureLocation.shaderArraySize)) {
             FlatBindingIndex textureGLIndex =
                 indices[combined.textureLocation.group][textureArrayStart + textureArrayElement];
             mUnitsForTextures[textureGLIndex].push_back(textureUnit);
@@ -140,11 +136,7 @@
                 mPlaceholderSamplerUnits.push_back(textureUnit);
             } else {
                 // Record that the sampler used in the shader must be set for this texture unit.
-                const BindGroupLayoutInternalBase* samplerBgl =
-                    layout->GetBindGroupLayout(combined.samplerLocation->group);
-                BindingIndex samplerBindingIndex =
-                    samplerBgl->GetBindingIndex(combined.samplerLocation->binding);
-
+                BindingIndex samplerBindingIndex = combined.samplerLocation->index;
                 FlatBindingIndex samplerGLIndex =
                     indices[combined.samplerLocation->group][samplerBindingIndex];
                 mUnitsForSamplers[samplerGLIndex].push_back(textureUnit);
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index 971f550..026b6bd 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -105,8 +105,6 @@
     DAWN_UNREACHABLE();
 }
 
-using BindingMap = absl::flat_hash_map<tint::BindingPoint, tint::BindingPoint>;
-
 // Returns information about the texture/sampler pairs used by the entry point. This is necessary
 // because GL uses combined texture/sampler bindings while WGSL allows mixing and matching textures
 // and samplers in the shader. GL also uses a placeholder sampler to use with textures when they
@@ -117,77 +115,98 @@
 void GenerateCombinedSamplerInfo(
     const EntryPointMetadata& metadata,
     const tint::glsl::writer::Bindings& bindings,
-    const BindingMap& externalTextureExpansionMap,
+    const PipelineLayout* layout,
     std::vector<CombinedSampler>* combinedSamplers,
     tint::glsl::writer::CombinedTextureSamplerInfo* samplerTextureToName) {
-    // Helper to avoid duplicated logic for when a CombinedSampler is determined.
-    auto AddCombinedSampler = [&](tint::BindingPoint textureWGSL,
-                                  tint::BindingPoint textureRemapped,
-                                  std::optional<tint::BindingPoint> samplerWGSL,
-                                  BindingIndex textureArraySize, bool isPlane1 = false) {
-        // Dawn needs pre-remapping WGSL bind points.
+    // Helper to avoid duplicated logic for when a CombinedSampler is determined. It takes a bunch
+    // of information for both the texture and the sampler and translate to what Dawn/Tint need.
+    struct CombinedBindingInfo {
+        // Dawn takes BindGroupIndex + BindingIndex.
+        BindGroupIndex group;
+        BindingIndex index;
+        BindingIndex shaderArraySize = BindingIndex(1);
+        // Tint takes the post-remapping binding point.
+        tint::glsl::writer::BindingInfo remappedBinding;
+    };
+    auto AddCombinedSampler = [&](CombinedBindingInfo texture,
+                                  std::optional<CombinedBindingInfo> sampler,
+                                  bool isPlane1 = false) {
+        // Reflect to the pipeline the combination with BindGroupIndex + BindingIndex in that BGL.
         CombinedSampler combinedSampler = {{
             .samplerLocation = std::nullopt,
             .textureLocation = {{
-                .group = BindGroupIndex(textureWGSL.group),
-                .binding = BindingNumber(textureWGSL.binding),
-                .arraySize = textureArraySize,
+                .group = texture.group,
+                .index = texture.index,
+                .shaderArraySize = texture.shaderArraySize,
             }},
         }};
-        if (samplerWGSL.has_value()) {
+        if (sampler.has_value()) {
             combinedSampler.samplerLocation = {{{
-                .group = BindGroupIndex(samplerWGSL->group),
-                .binding = BindingNumber(samplerWGSL->binding),
+                .group = sampler->group,
+                .index = sampler->index,
+                .shaderArraySize = sampler->shaderArraySize,
             }}};
         }
         combinedSamplers->push_back(combinedSampler);
 
-        // Tint uses post-remapping bind points.
+        // Let Tint know to generate a new GLSL sampler for this combination.
         tint::BindingPoint samplerRemapped = bindings.placeholder_sampler_bind_point;
-        if (samplerWGSL.has_value()) {
-            samplerRemapped = {.group = 0,
-                               .binding = bindings.sampler.at(samplerWGSL.value()).binding};
+        if (sampler.has_value()) {
+            samplerRemapped = {0, sampler->remappedBinding.binding};
         }
-
         samplerTextureToName->emplace(
-            tint::glsl::writer::CombinedTextureSamplerPair{textureRemapped, samplerRemapped,
-                                                           isPlane1},
+            tint::glsl::writer::CombinedTextureSamplerPair{
+                {0, texture.remappedBinding.binding}, samplerRemapped, isPlane1},
             combinedSampler.GetName());
     };
 
     for (const auto& use : metadata.samplerAndNonSamplerTexturePairs) {
         // Replace uses of the placeholder sampler with its actual binding point.
-        std::optional<tint::BindingPoint> sampler = std::nullopt;
+        std::optional<CombinedBindingInfo> sampler = std::nullopt;
         if (use.sampler != EntryPointMetadata::nonSamplerBindingPoint) {
-            sampler = ToTint(use.sampler);
+            const BindGroupLayoutInternalBase* bgl = layout->GetBindGroupLayout(use.sampler.group);
+            sampler = {
+                .group = use.sampler.group,
+                .index = bgl->AsBindingIndex(bgl->GetBindingMap().at(use.sampler.binding)),
+                .remappedBinding = bindings.sampler.at(ToTint(use.sampler)),
+            };
         }
 
         // Tint reflection returns information about uses of both regular textures and sampled
         // textures so we need to differentiate both cases here.
+        const BindGroupLayoutInternalBase* bgl = layout->GetBindGroupLayout(use.texture.group);
+        APIBindingIndex textureAPIIndex = bgl->GetBindingMap().at(use.texture.binding);
+        const auto& bindingInfo = bgl->GetAPIBindingInfo(textureAPIIndex);
 
         // The easy case is when a regular texture is being handled.
-        if (!externalTextureExpansionMap.contains(ToTint(use.texture))) {
-            tint::BindingPoint textureWGSL = ToTint(use.texture);
-            tint::BindingPoint textureRemapped = {0, bindings.texture.at(textureWGSL).binding};
-            BindingIndex arraySizeInShader = metadata.bindings.at(BindGroupIndex(textureWGSL.group))
-                                                 .at(BindingNumber(textureWGSL.binding))
-                                                 .arraySize;
-            AddCombinedSampler(textureWGSL, textureRemapped, sampler, arraySizeInShader);
+        if (std::holds_alternative<TextureBindingInfo>(bindingInfo.bindingLayout)) {
+            CombinedBindingInfo texture = {
+                .group = use.texture.group,
+                .index = bgl->AsBindingIndex(textureAPIIndex),
+                .shaderArraySize =
+                    metadata.bindings.at(use.texture.group).at(use.texture.binding).arraySize,
+                .remappedBinding = bindings.texture.at(ToTint(use.texture)),
+            };
+            AddCombinedSampler(texture, sampler);
             continue;
         }
 
-        // Add plane 0 of the external texture (this happen to be the same code as for regular
-        // textures because plane0 uses the original WGSL bind point).
-        tint::BindingPoint plane0WGSL = ToTint(use.texture);
-        tint::BindingPoint plane0Remapped = {
-            0, bindings.external_texture.at(plane0WGSL).plane0.binding};
-        AddCombinedSampler(plane0WGSL, plane0Remapped, sampler, BindingIndex(1));
+        // This is an external texture, add planes individually.
+        const auto& bindingLayout = std::get<ExternalTextureBindingInfo>(bindingInfo.bindingLayout);
 
-        // Plane 1 needs its pre-remapping bind point queried from the expansion map.
-        tint::BindingPoint plane1WGSL = externalTextureExpansionMap.at(plane0WGSL);
-        tint::BindingPoint plane1Remapped = {
-            0, bindings.external_texture.at(plane0WGSL).plane1.binding};
-        AddCombinedSampler(plane1WGSL, plane1Remapped, sampler, BindingIndex(1), true);
+        CombinedBindingInfo plane0 = {
+            .group = use.texture.group,
+            .index = bindingLayout.plane0,
+            .remappedBinding = bindings.external_texture.at(ToTint(use.texture)).plane0,
+        };
+        AddCombinedSampler(plane0, sampler, false);
+
+        CombinedBindingInfo plane1 = {
+            .group = use.texture.group,
+            .index = bindingLayout.plane1,
+            .remappedBinding = bindings.external_texture.at(ToTint(use.texture)).plane1,
+        };
+        AddCombinedSampler(plane1, sampler, true);
     }
 }
 
@@ -281,7 +300,8 @@
 }
 
 bool operator<(const CombinedSamplerElement& a, const CombinedSamplerElement& b) {
-    return std::tie(a.group, a.binding, a.arraySize) < std::tie(b.group, b.binding, b.arraySize);
+    return std::tie(a.group, a.index, a.shaderArraySize) <
+           std::tie(b.group, b.index, b.shaderArraySize);
 }
 
 bool operator<(const CombinedSampler& a, const CombinedSampler& b) {
@@ -296,10 +316,10 @@
         o << "_placeholder_sampler";
     } else {
         o << "_" << static_cast<uint32_t>(samplerLocation->group) << "_"
-          << static_cast<uint32_t>(samplerLocation->binding);
+          << static_cast<uint32_t>(samplerLocation->index);
     }
     o << "_with_" << static_cast<uint32_t>(textureLocation.group) << "_"
-      << static_cast<uint32_t>(textureLocation.binding);
+      << static_cast<uint32_t>(textureLocation.index);
     return o.str();
 }
 
@@ -318,33 +338,32 @@
                            std::vector<tint::wgsl::Extension> internalExtensions)
     : ShaderModuleBase(device, descriptor, std::move(internalExtensions)) {}
 
-std::pair<tint::glsl::writer::Bindings, BindingMap> GenerateBindingInfo(
-    SingleShaderStage stage,
-    const PipelineLayout* layout,
-    const BindingInfoArray& moduleBindingInfo,
-    GLSLCompilationRequest& req) {
-    // Because of the way the rest of the backend uses the binding information, we need to pass
-    // through the original WGSL values in the combined shader map. That means, we need to store
-    // that data for the external texture, otherwise it ends up getting lost.
-    BindingMap externalTextureExpansionMap;
-
+tint::glsl::writer::Bindings GenerateBindingInfo(SingleShaderStage stage,
+                                                 const PipelineLayout* layout,
+                                                 const BindingInfoArray& moduleBindingInfo,
+                                                 GLSLCompilationRequest& req) {
     tint::glsl::writer::Bindings bindings;
 
     for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
         const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
+        const auto& bindingIndexInfo = layout->GetBindingIndexInfo()[group];
 
-        for (const auto& [binding, shaderBindingInfo] : moduleBindingInfo[group]) {
-            tint::BindingPoint srcBindingPoint{static_cast<uint32_t>(group),
-                                               static_cast<uint32_t>(binding)};
+        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
+            tint::BindingPoint srcBindingPoint{
+                .group = uint32_t(group),
+                .binding = uint32_t(bindingNumber),
+            };
 
-            BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
-            const auto& bindingIndexInfo = layout->GetBindingIndexInfo()[group];
-            FlatBindingIndex shaderIndex = bindingIndexInfo[bindingIndex];
-            tint::glsl::writer::BindingInfo dstBindingPoint{uint32_t(shaderIndex)};
+            auto ComputeDestinationBindingPoint = [&](BindingIndex bindingIndex) {
+                return tint::glsl::writer::BindingInfo{
+                    .binding = uint32_t(bindingIndexInfo[bindingIndex])};
+            };
 
             MatchVariant(
-                shaderBindingInfo.bindingInfo,
+                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                 [&](const BufferBindingInfo& bindingInfo) {
+                    tint::glsl::writer::BindingInfo dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     switch (bindingInfo.type) {
                         case wgpu::BufferBindingType::Uniform:
                             bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
@@ -362,43 +381,41 @@
                     }
                 },
                 [&](const SamplerBindingInfo& bindingInfo) {
-                    bindings.sampler.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
+                },
+                [&](const StaticSamplerBindingInfo& bindingInfo) {
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TextureBindingInfo& bindingInfo) {
-                    bindings.texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const StorageTextureBindingInfo& bindingInfo) {
-                    bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.storage_texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
+                },
+                [&](const TexelBufferBindingInfo& bindingInfo) {
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
                 },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
-                    const auto& etBindingMap = bgl->GetExternalTextureBindingExpansionMap();
-                    const auto& expansion = etBindingMap.find(binding);
-                    DAWN_ASSERT(expansion != etBindingMap.end());
-
-                    using BindingInfo = tint::glsl::writer::BindingInfo;
-
-                    const auto& bindingExpansion = expansion->second;
-                    const BindingInfo plane0{
-                        uint32_t(bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.plane0)])};
-                    const BindingInfo plane1{
-                        uint32_t(bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.plane1)])};
-                    const BindingInfo metadata{
-                        uint32_t(bindingIndexInfo[bgl->GetBindingIndex(bindingExpansion.params)])};
-
-                    tint::BindingPoint plane1WGSLBindingPoint{
-                        static_cast<uint32_t>(group),
-                        static_cast<uint32_t>(bindingExpansion.plane1)};
-                    externalTextureExpansionMap[srcBindingPoint] = plane1WGSLBindingPoint;
-
                     bindings.external_texture.emplace(
                         srcBindingPoint,
-                        tint::glsl::writer::ExternalTexture{metadata, plane0, plane1});
+                        tint::glsl::writer::ExternalTexture{
+                            .metadata = ComputeDestinationBindingPoint(bindingInfo.params),
+                            .plane0 = ComputeDestinationBindingPoint(bindingInfo.plane0),
+                            .plane1 = ComputeDestinationBindingPoint(bindingInfo.plane1)});
                 },
-                [&](const TexelBufferBindingInfo& bindingInfo) { DAWN_UNREACHABLE(); },
-                [&](const InputAttachmentBindingInfo& bindingInfo) { DAWN_UNREACHABLE(); });
+                [](const InputAttachmentBindingInfo& bindingInfo) { DAWN_UNREACHABLE(); });
         }
     }
-    return {bindings, externalTextureExpansionMap};
+    return bindings;
 }
 
 ResultOrError<GLuint> ShaderModule::CompileShader(
@@ -428,8 +445,7 @@
     const EntryPointMetadata& entryPointMetaData = GetEntryPoint(programmableStage.entryPoint);
     const BindingInfoArray& moduleBindingInfo = entryPointMetaData.bindings;
 
-    auto [bindings, externalTextureExpansionMap] =
-        GenerateBindingInfo(stage, layout, moduleBindingInfo, req);
+    auto bindings = GenerateBindingInfo(stage, layout, moduleBindingInfo, req);
 
     // When textures are accessed without a sampler (e.g., textureLoad()), returned
     // CombinedSamplerInfo should use this sentinel value as sampler binding point.
@@ -440,8 +456,8 @@
     {
         std::vector<CombinedSampler> combinedSamplers;
         tint::glsl::writer::CombinedTextureSamplerInfo samplerTextureToName;
-        GenerateCombinedSamplerInfo(entryPointMetaData, bindings, externalTextureExpansionMap,
-                                    &combinedSamplers, &samplerTextureToName);
+        GenerateCombinedSamplerInfo(entryPointMetaData, bindings, layout, &combinedSamplers,
+                                    &samplerTextureToName);
 
         bindings.sampler_texture_to_name = std::move(samplerTextureToName);
         *combinedSamplersOut = std::move(combinedSamplers);
diff --git a/src/dawn/native/opengl/ShaderModuleGL.h b/src/dawn/native/opengl/ShaderModuleGL.h
index 38281b4..53743ea 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.h
+++ b/src/dawn/native/opengl/ShaderModuleGL.h
@@ -57,10 +57,10 @@
 
 #define COMBINED_SAMPLER_ELEMENT_MEMBERS(X)                                                 \
     X(BindGroupIndex, group)                                                                \
-    X(BindingNumber, binding)                                                               \
+    X(BindingIndex, index)                                                                  \
     /* Return the array size of the element in the WGSL / GLSL as OpenGL requires that a */ \
     /* non-arrayed (arraySize = 1) binding uses glUniform1i and not glUniform1iv. */        \
-    X(BindingIndex, arraySize, 1)
+    X(BindingIndex, shaderArraySize, 1)
 DAWN_SERIALIZABLE(struct, CombinedSamplerElement, COMBINED_SAMPLER_ELEMENT_MEMBERS){};
 #undef COMBINED_SAMPLER_ELEMENT_MEMBERS
 
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index e597736..32b7e47 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -137,27 +137,25 @@
     tint::Bindings bindings;
     std::unordered_set<tint::BindingPoint> statically_paired_texture_binding_points;
 
-    const BindingInfoArray& moduleBindingInfo =
-        GetEntryPoint(programmableStage.entryPoint.c_str()).bindings;
-
     for (BindGroupIndex group : layout->GetBindGroupLayoutsMask()) {
         const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
 
-        for (const auto& currentModuleBindingInfo : moduleBindingInfo[group]) {
-            // We cannot use structured binding here because lambda expressions can only capture
-            // variables, while structured binding doesn't introduce variables.
-            const auto& binding = currentModuleBindingInfo.first;
-            const auto& shaderBindingInfo = currentModuleBindingInfo.second;
+        for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) {
+            tint::BindingPoint srcBindingPoint{
+                .group = uint32_t(group),
+                .binding = uint32_t(bindingNumber),
+            };
 
-            tint::BindingPoint srcBindingPoint{static_cast<uint32_t>(group),
-                                               static_cast<uint32_t>(binding)};
-
-            tint::BindingPoint dstBindingPoint{
-                static_cast<uint32_t>(group), static_cast<uint32_t>(bgl->GetBindingIndex(binding))};
+            auto ComputeDestinationBindingPoint = [&](BindingIndex bindingIndex) {
+                return tint::BindingPoint{.group = uint32_t(group),
+                                          .binding = uint32_t(bindingIndex)};
+            };
 
             MatchVariant(
-                shaderBindingInfo.bindingInfo,
+                bgl->GetAPIBindingInfo(apiBindingIndex).bindingLayout,
                 [&](const BufferBindingInfo& bindingInfo) {
+                    tint::BindingPoint dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     switch (bindingInfo.type) {
                         case wgpu::BufferBindingType::Uniform:
                             bindings.uniform.emplace(srcBindingPoint, dstBindingPoint);
@@ -175,44 +173,46 @@
                     }
                 },
                 [&](const SamplerBindingInfo& bindingInfo) {
-                    bindings.sampler.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
+                },
+                [&](const StaticSamplerBindingInfo& bindingInfo) {
+                    bindings.sampler.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TextureBindingInfo& bindingInfo) {
+                    tint::BindingPoint dstBindingPoint =
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex));
                     if (auto samplerIndex = bgl->GetStaticSamplerIndexForTexture(
                             BindingIndex{dstBindingPoint.binding})) {
-                        dstBindingPoint.binding = static_cast<uint32_t>(samplerIndex.value());
+                        dstBindingPoint.binding = uint32_t(samplerIndex.value());
                         statically_paired_texture_binding_points.insert(srcBindingPoint);
                     }
                     bindings.texture.emplace(srcBindingPoint, dstBindingPoint);
                 },
                 [&](const StorageTextureBindingInfo& bindingInfo) {
-                    bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.storage_texture.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 },
                 [&](const TexelBufferBindingInfo& bindingInfo) {
                     // TODO(crbug/382544164): Prototype texel buffer feature
                     DAWN_UNREACHABLE();
                 },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
-                    const auto& bindingMap = bgl->GetExternalTextureBindingExpansionMap();
-                    const auto& expansion = bindingMap.find(binding);
-                    DAWN_ASSERT(expansion != bindingMap.end());
-
-                    const auto& bindingExpansion = expansion->second;
-                    tint::BindingPoint plane0{
-                        static_cast<uint32_t>(group),
-                        static_cast<uint32_t>(bgl->GetBindingIndex(bindingExpansion.plane0))};
-                    tint::BindingPoint plane1{
-                        static_cast<uint32_t>(group),
-                        static_cast<uint32_t>(bgl->GetBindingIndex(bindingExpansion.plane1))};
-                    tint::BindingPoint metadata{
-                        static_cast<uint32_t>(group),
-                        static_cast<uint32_t>(bgl->GetBindingIndex(bindingExpansion.params))};
-
                     bindings.external_texture.emplace(
-                        srcBindingPoint, tint::ExternalTexture{metadata, plane0, plane1});
+                        srcBindingPoint,
+                        tint::ExternalTexture{
+                            .metadata = ComputeDestinationBindingPoint(bindingInfo.params),
+                            .plane0 = ComputeDestinationBindingPoint(bindingInfo.plane0),
+                            .plane1 = ComputeDestinationBindingPoint(bindingInfo.plane1)});
                 },
                 [&](const InputAttachmentBindingInfo& bindingInfo) {
-                    bindings.input_attachment.emplace(srcBindingPoint, dstBindingPoint);
+                    bindings.input_attachment.emplace(
+                        srcBindingPoint,
+                        ComputeDestinationBindingPoint(bgl->AsBindingIndex(apiBindingIndex)));
                 });
         }
     }