Save all binding layouts in an std::variant in BindingInfo

This patch uses std::variant to represent 4 possible member types in
BindingInfo (BufferBindingLayout, SamplerBindingLayout,
TextureBindingLayout, StorageTextureBindingLayout) as logically
at any time only one of them can be used.

Bug: dawn:527
Change-Id: If83d8fa282a0d4092051ef54a97369d70a3fb531
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/172460
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 510019c..4e2a217 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -28,6 +28,7 @@
 #include "dawn/native/BindGroup.h"
 
 #include "dawn/common/Assert.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/common/Math.h"
 #include "dawn/common/ityp_bitset.h"
 #include "dawn/native/BindGroupLayout.h"
@@ -50,7 +51,7 @@
 
 MaybeError ValidateBufferBinding(const DeviceBase* device,
                                  const BindGroupEntry& entry,
-                                 const BindingInfo& bindingInfo) {
+                                 const BufferBindingLayout& layout) {
     DAWN_INVALID_IF(entry.buffer == nullptr, "Binding entry buffer not set.");
 
     DAWN_INVALID_IF(entry.sampler != nullptr || entry.textureView != nullptr,
@@ -60,8 +61,6 @@
 
     DAWN_TRY(device->ValidateObject(entry.buffer));
 
-    DAWN_ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer);
-
     uint64_t bufferSize = entry.buffer->GetSize();
 
     // Handle wgpu::WholeSize, avoiding overflows.
@@ -87,7 +86,7 @@
     wgpu::BufferUsage requiredUsage;
     uint64_t maxBindingSize;
     uint64_t requiredBindingAlignment;
-    switch (bindingInfo.buffer.type) {
+    switch (layout.type) {
         case wgpu::BufferBindingType::Uniform:
             requiredUsage = wgpu::BufferUsage::Uniform;
             maxBindingSize = device->GetLimits().v1.maxUniformBufferBindingSize;
@@ -101,7 +100,7 @@
             DAWN_INVALID_IF(
                 bindingSize % 4 != 0,
                 "Binding size (%u) of %s isn't a multiple of 4 when binding type is (%s).",
-                bindingSize, entry.buffer, bindingInfo.buffer.type);
+                bindingSize, entry.buffer, layout.type);
             break;
         case kInternalStorageBufferBinding:
             requiredUsage = kInternalStorageBuffer;
@@ -114,15 +113,15 @@
 
     DAWN_INVALID_IF(!IsAligned(entry.offset, requiredBindingAlignment),
                     "Offset (%u) of %s does not satisfy the minimum %s alignment (%u).",
-                    entry.offset, entry.buffer, bindingInfo.buffer.type, requiredBindingAlignment);
+                    entry.offset, entry.buffer, layout.type, requiredBindingAlignment);
 
     DAWN_INVALID_IF(!(entry.buffer->GetUsage() & requiredUsage),
                     "Binding usage (%s) of %s doesn't match expected usage (%s).",
                     entry.buffer->GetUsageExternalOnly(), entry.buffer, requiredUsage);
 
-    DAWN_INVALID_IF(bindingSize < bindingInfo.buffer.minBindingSize,
+    DAWN_INVALID_IF(bindingSize < layout.minBindingSize,
                     "Binding size (%u) of %s is smaller than the minimum binding size (%u).",
-                    bindingSize, entry.buffer, bindingInfo.buffer.minBindingSize);
+                    bindingSize, entry.buffer, layout.minBindingSize);
 
     DAWN_INVALID_IF(bindingSize > maxBindingSize,
                     "Binding size (%u) of %s is larger than the maximum binding size (%u).",
@@ -131,10 +130,7 @@
     return {};
 }
 
-MaybeError ValidateTextureBinding(DeviceBase* device,
-                                  const BindGroupEntry& entry,
-                                  const BindingInfo& bindingInfo,
-                                  UsageValidationMode mode) {
+MaybeError ValidateTextureBindGroupEntry(DeviceBase* device, const BindGroupEntry& entry) {
     DAWN_INVALID_IF(entry.textureView == nullptr, "Binding entry textureView not set.");
 
     DAWN_INVALID_IF(entry.sampler != nullptr || entry.buffer != nullptr,
@@ -142,84 +138,94 @@
 
     DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr.");
 
+    TextureViewBase* view = entry.textureView;
     DAWN_TRY(device->ValidateObject(entry.textureView));
 
+    Aspect aspect = view->GetAspects();
+    DAWN_INVALID_IF(!HasOneBit(aspect), "Multiple aspects (%s) selected in %s.", aspect, view);
+
+    return {};
+}
+
+MaybeError ValidateSampledTextureBinding(DeviceBase* device,
+                                         const BindGroupEntry& entry,
+                                         const TextureBindingLayout& layout,
+                                         UsageValidationMode mode) {
+    DAWN_TRY(ValidateTextureBindGroupEntry(device, entry));
+
     TextureViewBase* view = entry.textureView;
 
     Aspect aspect = view->GetAspects();
     DAWN_INVALID_IF(!HasOneBit(aspect), "Multiple aspects (%s) selected in %s.", aspect, view);
 
     TextureBase* texture = view->GetTexture();
-    switch (bindingInfo.bindingType) {
-        case BindingInfoType::Texture: {
-            SampleTypeBit supportedTypes =
-                texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes;
-            DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::TextureBinding, mode));
 
-            DAWN_INVALID_IF(texture->IsMultisampledTexture() != bindingInfo.texture.multisampled,
-                            "Sample count (%u) of %s doesn't match expectation (multisampled: %d).",
-                            texture->GetSampleCount(), texture, bindingInfo.texture.multisampled);
+    SampleTypeBit supportedTypes = texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes;
+    DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::TextureBinding, mode));
 
-            SampleTypeBit requiredType;
-            if (bindingInfo.texture.sampleType == kInternalResolveAttachmentSampleType) {
-                // If the binding's sample type is kInternalResolveAttachmentSampleType,
-                // then the supported types must contain float.
-                requiredType = SampleTypeBit::UnfilterableFloat;
-            } else {
-                requiredType = SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType);
-            }
+    DAWN_INVALID_IF(texture->IsMultisampledTexture() != layout.multisampled,
+                    "Sample count (%u) of %s doesn't match expectation (multisampled: %d).",
+                    texture->GetSampleCount(), texture, layout.multisampled);
 
-            DAWN_INVALID_IF(
-                !(supportedTypes & requiredType),
-                "None of the supported sample types (%s) of %s match the expected sample "
-                "types (%s).",
-                supportedTypes, texture, requiredType);
-
-            DAWN_INVALID_IF(entry.textureView->GetDimension() != bindingInfo.texture.viewDimension,
-                            "Dimension (%s) of %s doesn't match the expected dimension (%s).",
-                            entry.textureView->GetDimension(), entry.textureView,
-                            bindingInfo.texture.viewDimension);
-
-            DAWN_INVALID_IF(device->IsCompatibilityMode() &&
-                                entry.textureView->GetDimension() !=
-                                    texture->GetCompatibilityTextureBindingViewDimension(),
-                            "Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
-                            "%s in compatibility mode.",
-                            entry.textureView->GetDimension(), entry.textureView,
-                            texture->GetCompatibilityTextureBindingViewDimension(), texture);
-            break;
-        }
-        case BindingInfoType::StorageTexture: {
-            DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::StorageBinding, mode));
-
-            DAWN_ASSERT(!texture->IsMultisampledTexture());
-
-            DAWN_INVALID_IF(texture->GetFormat().format != bindingInfo.storageTexture.format,
-                            "Format (%s) of %s expected to be (%s).", texture->GetFormat().format,
-                            texture, bindingInfo.storageTexture.format);
-
-            DAWN_INVALID_IF(
-                entry.textureView->GetDimension() != bindingInfo.storageTexture.viewDimension,
-                "Dimension (%s) of %s doesn't match the expected dimension (%s).",
-                entry.textureView->GetDimension(), entry.textureView,
-                bindingInfo.storageTexture.viewDimension);
-
-            DAWN_INVALID_IF(entry.textureView->GetLevelCount() != 1,
-                            "mipLevelCount (%u) of %s expected to be 1.",
-                            entry.textureView->GetLevelCount(), entry.textureView);
-            break;
-        }
-        default:
-            DAWN_UNREACHABLE();
-            break;
+    SampleTypeBit requiredType;
+    if (layout.sampleType == kInternalResolveAttachmentSampleType) {
+        // If the binding's sample type is kInternalResolveAttachmentSampleType,
+        // then the supported types must contain float.
+        requiredType = SampleTypeBit::UnfilterableFloat;
+    } else {
+        requiredType = SampleTypeToSampleTypeBit(layout.sampleType);
     }
 
+    DAWN_INVALID_IF(!(supportedTypes & requiredType),
+                    "None of the supported sample types (%s) of %s match the expected sample "
+                    "types (%s).",
+                    supportedTypes, texture, requiredType);
+
+    DAWN_INVALID_IF(entry.textureView->GetDimension() != layout.viewDimension,
+                    "Dimension (%s) of %s doesn't match the expected dimension (%s).",
+                    entry.textureView->GetDimension(), entry.textureView, layout.viewDimension);
+
+    DAWN_INVALID_IF(
+        device->IsCompatibilityMode() && entry.textureView->GetDimension() !=
+                                             texture->GetCompatibilityTextureBindingViewDimension(),
+        "Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
+        "%s in compatibility mode.",
+        entry.textureView->GetDimension(), entry.textureView,
+        texture->GetCompatibilityTextureBindingViewDimension(), texture);
+
+    return {};
+}
+
+MaybeError ValidateStorageTextureBinding(DeviceBase* device,
+                                         const BindGroupEntry& entry,
+                                         const StorageTextureBindingLayout& layout,
+                                         UsageValidationMode mode) {
+    DAWN_TRY(ValidateTextureBindGroupEntry(device, entry));
+
+    TextureViewBase* view = entry.textureView;
+    TextureBase* texture = view->GetTexture();
+
+    DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::StorageBinding, mode));
+
+    DAWN_ASSERT(!texture->IsMultisampledTexture());
+
+    DAWN_INVALID_IF(texture->GetFormat().format != layout.format,
+                    "Format (%s) of %s expected to be (%s).", texture->GetFormat().format, texture,
+                    layout.format);
+
+    DAWN_INVALID_IF(view->GetDimension() != layout.viewDimension,
+                    "Dimension (%s) of %s doesn't match the expected dimension (%s).",
+                    view->GetDimension(), entry.textureView, layout.viewDimension);
+
+    DAWN_INVALID_IF(view->GetLevelCount() != 1, "mipLevelCount (%u) of %s expected to be 1.",
+                    view->GetLevelCount(), view);
+
     return {};
 }
 
 MaybeError ValidateSamplerBinding(const DeviceBase* device,
                                   const BindGroupEntry& entry,
-                                  const BindingInfo& bindingInfo) {
+                                  const SamplerBindingLayout& layout) {
     DAWN_INVALID_IF(entry.sampler == nullptr, "Binding entry sampler not set.");
 
     DAWN_INVALID_IF(entry.textureView != nullptr || entry.buffer != nullptr,
@@ -229,9 +235,7 @@
 
     DAWN_TRY(device->ValidateObject(entry.sampler));
 
-    DAWN_ASSERT(bindingInfo.bindingType == BindingInfoType::Sampler);
-
-    switch (bindingInfo.sampler.type) {
+    switch (layout.type) {
         case wgpu::SamplerBindingType::NonFiltering:
             DAWN_INVALID_IF(entry.sampler->IsFiltering(),
                             "Filtering sampler %s is incompatible with non-filtering sampler "
@@ -283,7 +287,9 @@
 void ForEachUnverifiedBufferBindingIndexImpl(const BindGroupLayoutInternalBase* bgl, F&& f) {
     uint32_t packedIndex = 0;
     for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBufferCount(); ++bindingIndex) {
-        if (bgl->GetBindingInfo(bindingIndex).buffer.minBindingSize == 0) {
+        const auto* bufferLayout =
+            std::get_if<BufferBindingLayout>(&bgl->GetBindingInfo(bindingIndex).bindingLayout);
+        if (bufferLayout == nullptr || bufferLayout->minBindingSize == 0) {
             f(bindingIndex, packedIndex++);
         }
     }
@@ -353,31 +359,37 @@
         const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);
 
         // Perform binding-type specific validation.
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer:
+        DAWN_TRY(MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout& layout) -> MaybeError {
                 // TODO(dawn:1485): Validate buffer binding with usage validation mode.
-                DAWN_TRY_CONTEXT(ValidateBufferBinding(device, entry, bindingInfo),
+                DAWN_TRY_CONTEXT(ValidateBufferBinding(device, entry, layout),
                                  "validating entries[%u] as a Buffer."
                                  "\nExpected entry layout: %s",
-                                 i, bindingInfo);
-                break;
-            case BindingInfoType::Texture:
-            case BindingInfoType::StorageTexture:
-                DAWN_TRY_CONTEXT(ValidateTextureBinding(device, entry, bindingInfo, mode),
-                                 "validating entries[%u] as a Texture."
+                                 i, layout);
+                return {};
+            },
+            [&](const TextureBindingLayout& layout) -> MaybeError {
+                DAWN_TRY_CONTEXT(ValidateSampledTextureBinding(device, entry, layout, mode),
+                                 "validating entries[%u] as a Sampled Texture."
                                  "\nExpected entry layout: %s",
-                                 i, bindingInfo);
-                break;
-            case BindingInfoType::Sampler:
-                DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, bindingInfo),
+                                 i, layout);
+                return {};
+            },
+            [&](const StorageTextureBindingLayout& layout) -> MaybeError {
+                DAWN_TRY_CONTEXT(ValidateStorageTextureBinding(device, entry, layout, mode),
+                                 "validating entries[%u] as a Storage Texture."
+                                 "\nExpected entry layout: %s",
+                                 i, layout);
+                return {};
+            },
+            [&](const SamplerBindingLayout& layout) -> MaybeError {
+                DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, layout),
                                  "validating entries[%u] as a Sampler."
                                  "\nExpected entry layout: %s",
-                                 i, bindingInfo);
-                break;
-            case BindingInfoType::ExternalTexture:
-                DAWN_UNREACHABLE();
-                break;
-        }
+                                 i, layout);
+                return {};
+            }));
     }
 
     // This should always be true because
@@ -545,7 +557,8 @@
     DAWN_ASSERT(!IsError());
     const BindGroupLayoutInternalBase* layout = GetLayout();
     DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
-    DAWN_ASSERT(layout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Buffer);
+    DAWN_ASSERT(std::holds_alternative<BufferBindingLayout>(
+        layout->GetBindingInfo(bindingIndex).bindingLayout));
     BufferBase* buffer = static_cast<BufferBase*>(mBindingData.bindings[bindingIndex].Get());
     return {buffer, mBindingData.bufferData[bindingIndex].offset,
             mBindingData.bufferData[bindingIndex].size};
@@ -555,7 +568,8 @@
     DAWN_ASSERT(!IsError());
     const BindGroupLayoutInternalBase* layout = GetLayout();
     DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
-    DAWN_ASSERT(layout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Sampler);
+    DAWN_ASSERT(std::holds_alternative<SamplerBindingLayout>(
+        layout->GetBindingInfo(bindingIndex).bindingLayout));
     return static_cast<SamplerBase*>(mBindingData.bindings[bindingIndex].Get());
 }
 
@@ -563,9 +577,10 @@
     DAWN_ASSERT(!IsError());
     const BindGroupLayoutInternalBase* layout = GetLayout();
     DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
-    DAWN_ASSERT(layout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Texture ||
-                layout->GetBindingInfo(bindingIndex).bindingType ==
-                    BindingInfoType::StorageTexture);
+    DAWN_ASSERT(std::holds_alternative<TextureBindingLayout>(
+                    layout->GetBindingInfo(bindingIndex).bindingLayout) ||
+                std::holds_alternative<StorageTextureBindingLayout>(
+                    layout->GetBindingInfo(bindingIndex).bindingLayout));
     return static_cast<TextureViewBase*>(mBindingData.bindings[bindingIndex].Get());
 }
 
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index b609fc4..33808da 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -37,6 +37,7 @@
 
 #include "dawn/common/BitSetIterator.h"
 #include "dawn/common/Enumerator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/ChainUtils.h"
 #include "dawn/native/Device.h"
 #include "dawn/native/ObjectBase.h"
@@ -351,29 +352,35 @@
 namespace {
 
 bool operator!=(const BindingInfo& a, const BindingInfo& b) {
-    if (a.visibility != b.visibility || a.bindingType != b.bindingType) {
+    if (a.visibility != b.visibility || a.bindingLayout.index() != b.bindingLayout.index()) {
         return true;
     }
 
-    switch (a.bindingType) {
-        case BindingInfoType::Buffer:
-            return a.buffer.type != b.buffer.type ||
-                   a.buffer.hasDynamicOffset != b.buffer.hasDynamicOffset ||
-                   a.buffer.minBindingSize != b.buffer.minBindingSize;
-        case BindingInfoType::Sampler:
-            return a.sampler.type != b.sampler.type;
-        case BindingInfoType::Texture:
-            return a.texture.sampleType != b.texture.sampleType ||
-                   a.texture.viewDimension != b.texture.viewDimension ||
-                   a.texture.multisampled != b.texture.multisampled;
-        case BindingInfoType::StorageTexture:
-            return a.storageTexture.access != b.storageTexture.access ||
-                   a.storageTexture.viewDimension != b.storageTexture.viewDimension ||
-                   a.storageTexture.format != b.storageTexture.format;
-        case BindingInfoType::ExternalTexture:
-            return false;
-    }
-    DAWN_UNREACHABLE();
+    return MatchVariant(
+        a.bindingLayout,
+        [&](const BufferBindingLayout& layoutA) -> bool {
+            const BufferBindingLayout& layoutB = std::get<BufferBindingLayout>(b.bindingLayout);
+            return layoutA.type != layoutB.type ||
+                   layoutA.hasDynamicOffset != layoutB.hasDynamicOffset ||
+                   layoutA.minBindingSize != layoutB.minBindingSize;
+        },
+        [&](const SamplerBindingLayout& layoutA) -> bool {
+            const SamplerBindingLayout& layoutB = std::get<SamplerBindingLayout>(b.bindingLayout);
+            return layoutA.type != layoutB.type;
+        },
+        [&](const TextureBindingLayout& layoutA) -> bool {
+            const TextureBindingLayout& layoutB = std::get<TextureBindingLayout>(b.bindingLayout);
+            return layoutA.sampleType != layoutB.sampleType ||
+                   layoutA.viewDimension != layoutB.viewDimension ||
+                   layoutA.multisampled != layoutB.multisampled;
+        },
+        [&](const StorageTextureBindingLayout& layoutA) -> bool {
+            const StorageTextureBindingLayout& layoutB =
+                std::get<StorageTextureBindingLayout>(b.bindingLayout);
+            return layoutA.access != layoutB.access ||
+                   layoutA.viewDimension != layoutB.viewDimension ||
+                   layoutA.format != layoutB.format;
+        });
 }
 
 bool IsBufferBinding(const UnpackedPtr<BindGroupLayoutEntry>& binding) {
@@ -393,29 +400,24 @@
     bindingInfo.visibility = binding->visibility;
 
     if (binding->buffer.type != wgpu::BufferBindingType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Buffer;
-        bindingInfo.buffer = binding->buffer;
+        bindingInfo.bindingLayout = binding->buffer;
     } else if (binding->sampler.type != wgpu::SamplerBindingType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Sampler;
-        bindingInfo.sampler = binding->sampler;
+        bindingInfo.bindingLayout = binding->sampler;
     } else if (binding->texture.sampleType != wgpu::TextureSampleType::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::Texture;
-        bindingInfo.texture = binding->texture.WithTrivialFrontendDefaults();
-
+        TextureBindingLayout bindingLayout = binding->texture.WithTrivialFrontendDefaults();
         if (binding->texture.viewDimension == wgpu::TextureViewDimension::Undefined) {
-            bindingInfo.texture.viewDimension = wgpu::TextureViewDimension::e2D;
+            bindingLayout.viewDimension = wgpu::TextureViewDimension::e2D;
         }
+        bindingInfo.bindingLayout = bindingLayout;
     } else if (binding->storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
-        bindingInfo.bindingType = BindingInfoType::StorageTexture;
-        bindingInfo.storageTexture = binding->storageTexture.WithTrivialFrontendDefaults();
-
+        StorageTextureBindingLayout bindingLayout =
+            binding->storageTexture.WithTrivialFrontendDefaults();
         if (binding->storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) {
-            bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D;
+            bindingLayout.viewDimension = wgpu::TextureViewDimension::e2D;
         }
+        bindingInfo.bindingLayout = bindingLayout;
     } else {
-        if (auto* externalTextureBindingLayout = binding.Get<ExternalTextureBindingLayout>()) {
-            bindingInfo.bindingType = BindingInfoType::ExternalTexture;
-        }
+        DAWN_UNREACHABLE();
     }
 
     return bindingInfo;
@@ -459,48 +461,61 @@
     BindingInfo bInfo = CreateBindGroupLayoutInfo(b);
 
     // Sort by type.
-    if (aInfo.bindingType != bInfo.bindingType) {
-        return aInfo.bindingType < bInfo.bindingType;
+    if (aInfo.bindingLayout.index() != bInfo.bindingLayout.index()) {
+        return GetBindingInfoType(aInfo) < GetBindingInfoType(bInfo);
     }
 
     if (a->visibility != b->visibility) {
         return a->visibility < b->visibility;
     }
 
-    switch (aInfo.bindingType) {
-        case BindingInfoType::Buffer:
-            if (aInfo.buffer.minBindingSize != bInfo.buffer.minBindingSize) {
-                return aInfo.buffer.minBindingSize < bInfo.buffer.minBindingSize;
+    switch (GetBindingInfoType(aInfo)) {
+        case BindingInfoType::Buffer: {
+            const auto& aLayout = std::get<BufferBindingLayout>(aInfo.bindingLayout);
+            const auto& bLayout = std::get<BufferBindingLayout>(bInfo.bindingLayout);
+            if (aLayout.minBindingSize != bLayout.minBindingSize) {
+                return aLayout.minBindingSize < bLayout.minBindingSize;
             }
             break;
-        case BindingInfoType::Sampler:
-            if (aInfo.sampler.type != bInfo.sampler.type) {
-                return aInfo.sampler.type < bInfo.sampler.type;
+        }
+        case BindingInfoType::Sampler: {
+            const auto& aLayout = std::get<SamplerBindingLayout>(aInfo.bindingLayout);
+            const auto& bLayout = std::get<SamplerBindingLayout>(bInfo.bindingLayout);
+            if (aLayout.type != bLayout.type) {
+                return aLayout.type < bLayout.type;
             }
             break;
-        case BindingInfoType::Texture:
-            if (aInfo.texture.multisampled != bInfo.texture.multisampled) {
-                return aInfo.texture.multisampled < bInfo.texture.multisampled;
+        }
+        case BindingInfoType::Texture: {
+            const auto& aLayout = std::get<TextureBindingLayout>(aInfo.bindingLayout);
+            const auto& bLayout = std::get<TextureBindingLayout>(bInfo.bindingLayout);
+            if (aLayout.multisampled != bLayout.multisampled) {
+                return aLayout.multisampled < bLayout.multisampled;
             }
-            if (aInfo.texture.viewDimension != bInfo.texture.viewDimension) {
-                return aInfo.texture.viewDimension < bInfo.texture.viewDimension;
+            if (aLayout.viewDimension != bLayout.viewDimension) {
+                return aLayout.viewDimension < bLayout.viewDimension;
             }
-            if (aInfo.texture.sampleType != bInfo.texture.sampleType) {
-                return aInfo.texture.sampleType < bInfo.texture.sampleType;
+            if (aLayout.sampleType != bLayout.sampleType) {
+                return aLayout.sampleType < bLayout.sampleType;
             }
             break;
-        case BindingInfoType::StorageTexture:
-            if (aInfo.storageTexture.access != bInfo.storageTexture.access) {
-                return aInfo.storageTexture.access < bInfo.storageTexture.access;
+        }
+        case BindingInfoType::StorageTexture: {
+            const auto& aLayout = std::get<StorageTextureBindingLayout>(aInfo.bindingLayout);
+            const auto& bLayout = std::get<StorageTextureBindingLayout>(bInfo.bindingLayout);
+            if (aLayout.access != bLayout.access) {
+                return aLayout.access < bLayout.access;
             }
-            if (aInfo.storageTexture.viewDimension != bInfo.storageTexture.viewDimension) {
-                return aInfo.storageTexture.viewDimension < bInfo.storageTexture.viewDimension;
+            if (aLayout.viewDimension != bLayout.viewDimension) {
+                return aLayout.viewDimension < bLayout.viewDimension;
             }
-            if (aInfo.storageTexture.format != bInfo.storageTexture.format) {
-                return aInfo.storageTexture.format < bInfo.storageTexture.format;
+            if (aLayout.format != bLayout.format) {
+                return aLayout.format < bLayout.format;
             }
             break;
+        }
         case BindingInfoType::ExternalTexture:
+            DAWN_UNREACHABLE();
             break;
     }
     return a->binding < b->binding;
@@ -512,7 +527,7 @@
     BindingIndex lastBufferIndex{0};
     BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max();
     for (auto [i, binding] : Enumerate(bindings)) {
-        if (binding.bindingType == BindingInfoType::Buffer) {
+        if (std::holds_alternative<BufferBindingLayout>(binding.bindingLayout)) {
             lastBufferIndex = std::max(i, lastBufferIndex);
         } else {
             firstNonBufferIndex = std::min(i, firstNonBufferIndex);
@@ -609,11 +624,25 @@
         recorder.Record(id, index);
 
         const BindingInfo& info = mBindingInfo[index];
-        recorder.Record(info.buffer.hasDynamicOffset, info.visibility, info.bindingType,
-                        info.buffer.type, info.buffer.minBindingSize, info.sampler.type,
-                        info.texture.sampleType, info.texture.viewDimension,
-                        info.texture.multisampled, info.storageTexture.access,
-                        info.storageTexture.format, info.storageTexture.viewDimension);
+        recorder.Record(info.visibility);
+
+        MatchVariant(
+            info.bindingLayout,
+            [&](const BufferBindingLayout& layout) {
+                recorder.Record(BindingInfoType::Buffer, layout.hasDynamicOffset, layout.type,
+                                layout.minBindingSize);
+            },
+            [&](const SamplerBindingLayout& layout) {
+                recorder.Record(BindingInfoType::Sampler, layout.type);
+            },
+            [&](const TextureBindingLayout& layout) {
+                recorder.Record(BindingInfoType::Texture, layout.sampleType, layout.viewDimension,
+                                layout.multisampled);
+            },
+            [&](const StorageTextureBindingLayout& layout) {
+                recorder.Record(BindingInfoType::StorageTexture, layout.access, layout.format,
+                                layout.viewDimension);
+            });
     }
 
     return recorder.GetContentHash();
@@ -705,7 +734,7 @@
 
 bool BindGroupLayoutInternalBase::IsStorageBufferBinding(BindingIndex bindingIndex) const {
     DAWN_ASSERT(bindingIndex < GetBufferCount());
-    switch (GetBindingInfo(bindingIndex).buffer.type) {
+    switch (std::get<BufferBindingLayout>(GetBindingInfo(bindingIndex).bindingLayout).type) {
         case wgpu::BufferBindingType::Uniform:
             return false;
         case kInternalStorageBufferBinding:
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index 220b3fe..ae8f3e6 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -27,11 +27,23 @@
 
 #include "dawn/native/BindingInfo.h"
 
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/ChainUtils.h"
 #include "dawn/native/Limits.h"
 
 namespace dawn::native {
 
+BindingInfoType GetBindingInfoType(const BindingInfo& info) {
+    return MatchVariant(
+        info.bindingLayout,
+        [](const BufferBindingLayout&) -> BindingInfoType { return BindingInfoType::Buffer; },
+        [](const SamplerBindingLayout&) -> BindingInfoType { return BindingInfoType::Sampler; },
+        [](const TextureBindingLayout&) -> BindingInfoType { return BindingInfoType::Texture; },
+        [](const StorageTextureBindingLayout&) -> BindingInfoType {
+            return BindingInfoType::StorageTexture;
+        });
+}
+
 void IncrementBindingCounts(BindingCounts* bindingCounts,
                             const UnpackedPtr<BindGroupLayoutEntry>& entry) {
     bindingCounts->totalCount += 1;
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h
index d21f22e..16970f6 100644
--- a/src/dawn/native/BindingInfo.h
+++ b/src/dawn/native/BindingInfo.h
@@ -29,6 +29,7 @@
 #define SRC_DAWN_NATIVE_BINDINGINFO_H_
 
 #include <cstdint>
+#include <variant>
 #include <vector>
 
 #include "dawn/common/Constants.h"
@@ -61,15 +62,15 @@
     BindingNumber binding;
     wgpu::ShaderStage visibility;
 
-    BindingInfoType bindingType;
-
-    // TODO(dawn:527): These four values could be made into a union.
-    BufferBindingLayout buffer;
-    SamplerBindingLayout sampler;
-    TextureBindingLayout texture;
-    StorageTextureBindingLayout storageTexture;
+    std::variant<BufferBindingLayout,
+                 SamplerBindingLayout,
+                 TextureBindingLayout,
+                 StorageTextureBindingLayout>
+        bindingLayout;
 };
 
+BindingInfoType GetBindingInfoType(const BindingInfo& bindingInfo);
+
 struct BindingSlot {
     BindGroupIndex group;
     BindingNumber binding;
diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp
index 2a24eed..c02fe7a 100644
--- a/src/dawn/native/CommandBufferStateTracker.cpp
+++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -124,11 +124,12 @@
         for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBufferCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
             // Buffer bindings are sorted to have smallest of bindingIndex.
-            DAWN_ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer);
+            const BufferBindingLayout& layout =
+                std::get<BufferBindingLayout>(bindingInfo.bindingLayout);
 
             // BindGroup validation already guarantees the buffer usage includes
             // wgpu::BufferUsage::Storage
-            if (bindingInfo.buffer.type != wgpu::BufferBindingType::Storage) {
+            if (layout.type != wgpu::BufferBindingType::Storage) {
                 continue;
             }
 
@@ -141,7 +142,7 @@
 
             uint64_t adjustedOffset = bufferBinding.offset;
             // Apply dynamic offset if any.
-            if (bindingInfo.buffer.hasDynamicOffset) {
+            if (layout.hasDynamicOffset) {
                 // SetBindGroup validation already guarantees offsets and sizes don't overflow.
                 adjustedOffset += dynamicOffsets[groupIndex][static_cast<uint32_t>(bindingIndex)];
             }
@@ -162,11 +163,13 @@
              bindingIndex < bgl->GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
 
-            if (bindingInfo.bindingType != BindingInfoType::StorageTexture) {
+            const auto* layout =
+                std::get_if<StorageTextureBindingLayout>(&bindingInfo.bindingLayout);
+            if (layout == nullptr) {
                 continue;
             }
 
-            switch (bindingInfo.storageTexture.access) {
+            switch (layout->access) {
                 case wgpu::StorageTextureAccess::WriteOnly:
                 case wgpu::StorageTextureAccess::ReadWrite:
                     break;
@@ -359,8 +362,8 @@
 
         for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
-            if (bindingInfo.bindingType != BindingInfoType::Texture &&
-                bindingInfo.bindingType != BindingInfoType::StorageTexture) {
+            if (!std::holds_alternative<TextureBindingLayout>(bindingInfo.bindingLayout) &&
+                !std::holds_alternative<StorageTextureBindingLayout>(bindingInfo.bindingLayout)) {
                 continue;
             }
 
@@ -672,11 +675,12 @@
                     mBindgroups[i]->GetUnverifiedBufferSizes()[packedIndex.value()];
                 uint64_t minBufferSize = (*mMinBufferSizes)[i][packedIndex.value()];
 
+                const auto& layout = std::get<BufferBindingLayout>(bindingInfo.bindingLayout);
                 return DAWN_VALIDATION_ERROR(
                     "%s bound with size %u at group %u, binding %u is too small. The pipeline (%s) "
                     "requires a buffer binding which is at least %u bytes.%s",
                     buffer, bufferSize, i, bindingNumber, mLastPipeline, minBufferSize,
-                    (bindingInfo.buffer.type == wgpu::BufferBindingType::Uniform
+                    (layout.type == wgpu::BufferBindingType::Uniform
                          ? " This binding is a uniform buffer binding. It is padded to a multiple "
                            "of 16 bytes, and as a result may be larger than the associated data in "
                            "the shader source."
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index b75e695..8a9e8e4 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -29,6 +29,7 @@
 
 #include <utility>
 
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroup.h"
 #include "dawn/native/Buffer.h"
 #include "dawn/native/EnumMaskIterator.h"
@@ -108,10 +109,11 @@
          ++bindingIndex) {
         const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
 
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
+        MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout& layout) {
                 BufferBase* buffer = group->GetBindingAsBufferBinding(bindingIndex).buffer;
-                switch (bindingInfo.buffer.type) {
+                switch (layout.type) {
                     case wgpu::BufferBindingType::Uniform:
                         BufferUsedAs(buffer, wgpu::BufferUsage::Uniform, bindingInfo.visibility);
                         break;
@@ -127,12 +129,10 @@
                     case wgpu::BufferBindingType::Undefined:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::Texture: {
+            },
+            [&](const TextureBindingLayout& layout) {
                 TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
-                switch (bindingInfo.texture.sampleType) {
+                switch (layout.sampleType) {
                     case kInternalResolveAttachmentSampleType:
                         TextureViewUsedAs(view, kResolveAttachmentLoadingUsage,
                                           bindingInfo.visibility);
@@ -142,12 +142,10 @@
                                           bindingInfo.visibility);
                         break;
                 }
-                break;
-            }
-
-            case BindingInfoType::StorageTexture: {
+            },
+            [&](const StorageTextureBindingLayout& layout) {
                 TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
-                switch (bindingInfo.storageTexture.access) {
+                switch (layout.access) {
                     case wgpu::StorageTextureAccess::WriteOnly:
                         TextureViewUsedAs(view, kWriteOnlyStorageTexture, bindingInfo.visibility);
                         break;
@@ -161,16 +159,8 @@
                     case wgpu::StorageTextureAccess::Undefined:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::ExternalTexture:
-                DAWN_UNREACHABLE();
-                break;
-
-            case BindingInfoType::Sampler:
-                break;
-        }
+            },
+            [&](const SamplerBindingLayout&) {});
     }
 
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
@@ -222,24 +212,20 @@
     for (BindingIndex index{0}; index < group->GetLayout()->GetBindingCount(); ++index) {
         const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(index);
 
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
+        MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout&) {
                 mUsage.referencedBuffers.insert(group->GetBindingAsBufferBinding(index).buffer);
-                break;
-            }
-
-            case BindingInfoType::Texture:
-            case BindingInfoType::StorageTexture: {
+            },
+            [&](const TextureBindingLayout&) {
                 mUsage.referencedTextures.insert(
                     group->GetBindingAsTextureView(index)->GetTexture());
-                break;
-            }
-
-            case BindingInfoType::ExternalTexture:
-                DAWN_UNREACHABLE();
-            case BindingInfoType::Sampler:
-                break;
-        }
+            },
+            [&](const StorageTextureBindingLayout&) {
+                mUsage.referencedTextures.insert(
+                    group->GetBindingAsTextureView(index)->GetTexture());
+            },
+            [](const SamplerBindingLayout&) {});
     }
 
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
diff --git a/src/dawn/native/ProgrammableEncoder.cpp b/src/dawn/native/ProgrammableEncoder.cpp
index 4d577d2..036b231 100644
--- a/src/dawn/native/ProgrammableEncoder.cpp
+++ b/src/dawn/native/ProgrammableEncoder.cpp
@@ -149,12 +149,12 @@
         const BindingInfo& bindingInfo = layout->GetBindingInfo(i);
 
         // BGL creation sorts bindings such that the dynamic buffer bindings are first.
-        // DAWN_ASSERT that this true.
-        DAWN_ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer);
-        DAWN_ASSERT(bindingInfo.buffer.hasDynamicOffset);
+        const BufferBindingLayout& bindingLayout =
+            std::get<BufferBindingLayout>(bindingInfo.bindingLayout);
+        DAWN_ASSERT(bindingLayout.hasDynamicOffset);
 
         uint64_t requiredAlignment;
-        switch (bindingInfo.buffer.type) {
+        switch (bindingLayout.type) {
             case wgpu::BufferBindingType::Uniform:
                 requiredAlignment = GetDevice()->GetLimits().v1.minUniformBufferOffsetAlignment;
                 break;
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 1898b61..b55ea40 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -383,7 +383,9 @@
 
     for (BindingIndex bindingIndex{0}; bindingIndex < layout->GetBufferCount(); ++bindingIndex) {
         const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);
-        if (bindingInfo.buffer.minBindingSize != 0) {
+        const auto* bufferBindingLayout =
+            std::get_if<BufferBindingLayout>(&bindingInfo.bindingLayout);
+        if (bufferBindingLayout == nullptr || bufferBindingLayout->minBindingSize > 0) {
             // Skip bindings that have minimum buffer size set in the layout
             continue;
         }
@@ -411,20 +413,20 @@
 }
 
 bool IsShaderCompatibleWithPipelineLayoutOnStorageTextureAccess(
-    const BindingInfo& bindingInfo,
-    const StorageTextureBindingInfo& bindingLayout) {
-    return bindingInfo.storageTexture.access == bindingLayout.access ||
-           (bindingInfo.storageTexture.access == wgpu::StorageTextureAccess::ReadWrite &&
-            bindingLayout.access == wgpu::StorageTextureAccess::WriteOnly);
+    const StorageTextureBindingLayout& pipelineBindingLayout,
+    const StorageTextureBindingInfo& shaderBindingInfo) {
+    return pipelineBindingLayout.access == shaderBindingInfo.access ||
+           (pipelineBindingLayout.access == wgpu::StorageTextureAccess::ReadWrite &&
+            shaderBindingInfo.access == wgpu::StorageTextureAccess::WriteOnly);
 }
 
 BindingInfoType GetShaderBindingType(const ShaderBindingInfo& shaderInfo) {
     return MatchVariant(
-        shaderInfo.bindingInfo, [&](const BufferBindingInfo&) { return BindingInfoType::Buffer; },
-        [&](const StorageTextureBindingInfo&) { return BindingInfoType::StorageTexture; },
-        [&](const SampledTextureBindingInfo&) { return BindingInfoType::Texture; },
-        [&](const SamplerBindingInfo&) { return BindingInfoType::Sampler; },
-        [&](const ExternalTextureBindingInfo&) { return BindingInfoType::ExternalTexture; });
+        shaderInfo.bindingInfo, [](const BufferBindingInfo&) { return BindingInfoType::Buffer; },
+        [](const SamplerBindingInfo&) { return BindingInfoType::Sampler; },
+        [](const SampledTextureBindingInfo&) { return BindingInfoType::Texture; },
+        [](const StorageTextureBindingInfo&) { return BindingInfoType::StorageTexture; },
+        [](const ExternalTextureBindingInfo&) { return BindingInfoType::ExternalTexture; });
 }
 
 MaybeError ValidateCompatibilityOfSingleBindingWithLayout(const DeviceBase* device,
@@ -460,10 +462,11 @@
     BindingIndex bindingIndex(bindingIt->second);
     const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex);
 
+    BindingInfoType bindingLayoutType = GetBindingInfoType(layoutInfo);
     BindingInfoType shaderBindingType = GetShaderBindingType(shaderInfo);
-    DAWN_INVALID_IF(layoutInfo.bindingType != shaderBindingType,
+    DAWN_INVALID_IF(bindingLayoutType != shaderBindingType,
                     "Binding type in the shader (%s) doesn't match the type in the layout (%s).",
-                    shaderBindingType, layoutInfo.bindingType);
+                    shaderBindingType, bindingLayoutType);
 
     ExternalTextureBindingExpansionMap expansions = layout->GetExternalTextureBindingExpansionMap();
     DAWN_INVALID_IF(expansions.find(bindingNumber) != expansions.end(),
@@ -477,21 +480,23 @@
     return MatchVariant(
         shaderInfo.bindingInfo,
         [&](const SampledTextureBindingInfo& bindingInfo) -> MaybeError {
+            const TextureBindingLayout& bindingLayout =
+                std::get<TextureBindingLayout>(layoutInfo.bindingLayout);
             DAWN_INVALID_IF(
-                layoutInfo.texture.multisampled != bindingInfo.multisampled,
+                bindingLayout.multisampled != bindingInfo.multisampled,
                 "Binding multisampled flag (%u) doesn't match the layout's multisampled "
                 "flag (%u)",
-                layoutInfo.texture.multisampled, bindingInfo.multisampled);
+                bindingLayout.multisampled, bindingInfo.multisampled);
 
             // TODO(dawn:563): Provide info about the sample types.
             SampleTypeBit requiredType;
-            if (layoutInfo.texture.sampleType == kInternalResolveAttachmentSampleType) {
+            if (bindingLayout.sampleType == kInternalResolveAttachmentSampleType) {
                 // If the layout's texture's sample type is
                 // kInternalResolveAttachmentSampleType, then the shader's compatible sample
                 // types must contain float.
                 requiredType = SampleTypeBit::UnfilterableFloat;
             } else {
-                requiredType = SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType);
+                requiredType = SampleTypeToSampleTypeBit(bindingLayout.sampleType);
             }
 
             DAWN_INVALID_IF(!(bindingInfo.compatibleSampleTypes & requiredType),
@@ -499,68 +504,72 @@
                             "sample type of the layout.");
 
             DAWN_INVALID_IF(
-                layoutInfo.texture.viewDimension != bindingInfo.viewDimension,
+                bindingLayout.viewDimension != bindingInfo.viewDimension,
                 "The shader's binding dimension (%s) doesn't match the shader's binding "
                 "dimension (%s).",
-                layoutInfo.texture.viewDimension, bindingInfo.viewDimension);
+                bindingLayout.viewDimension, bindingInfo.viewDimension);
             return {};
         },
         [&](const StorageTextureBindingInfo& bindingInfo) -> MaybeError {
-            DAWN_ASSERT(layoutInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
+            const StorageTextureBindingLayout& bindingLayout =
+                std::get<StorageTextureBindingLayout>(layoutInfo.bindingLayout);
+            DAWN_ASSERT(bindingLayout.format != wgpu::TextureFormat::Undefined);
             DAWN_ASSERT(bindingInfo.format != wgpu::TextureFormat::Undefined);
 
             DAWN_INVALID_IF(!IsShaderCompatibleWithPipelineLayoutOnStorageTextureAccess(
-                                layoutInfo, bindingInfo),
+                                bindingLayout, bindingInfo),
                             "The layout's binding access (%s) isn't compatible with the shader's "
                             "binding access (%s).",
-                            layoutInfo.storageTexture.access, bindingInfo.access);
+                            bindingLayout.access, bindingInfo.access);
 
-            DAWN_INVALID_IF(layoutInfo.storageTexture.format != bindingInfo.format,
+            DAWN_INVALID_IF(bindingLayout.format != bindingInfo.format,
                             "The layout's binding format (%s) doesn't match the shader's binding "
                             "format (%s).",
-                            layoutInfo.storageTexture.format, bindingInfo.format);
+                            bindingLayout.format, bindingInfo.format);
 
-            DAWN_INVALID_IF(layoutInfo.storageTexture.viewDimension != bindingInfo.viewDimension,
+            DAWN_INVALID_IF(bindingLayout.viewDimension != bindingInfo.viewDimension,
                             "The layout's binding dimension (%s) doesn't match the "
                             "shader's binding dimension (%s).",
-                            layoutInfo.storageTexture.viewDimension, bindingInfo.viewDimension);
+                            bindingLayout.viewDimension, bindingInfo.viewDimension);
             return {};
         },
         [&](const BufferBindingInfo& bindingInfo) -> MaybeError {
+            const BufferBindingLayout& bindingLayout =
+                std::get<BufferBindingLayout>(layoutInfo.bindingLayout);
             // Binding mismatch between shader and bind group is invalid. For example, a
             // writable binding in the shader with a readonly storage buffer in the bind
             // group layout is invalid. For internal usage with internal shaders, a storage
             // binding in the shader with an internal storage buffer in the bind group
             // layout is also valid.
-            bool validBindingConversion =
-                (layoutInfo.buffer.type == kInternalStorageBufferBinding &&
-                 bindingInfo.type == wgpu::BufferBindingType::Storage);
+            bool validBindingConversion = (bindingLayout.type == kInternalStorageBufferBinding &&
+                                           bindingInfo.type == wgpu::BufferBindingType::Storage);
 
             DAWN_INVALID_IF(
-                layoutInfo.buffer.type != bindingInfo.type && !validBindingConversion,
+                bindingLayout.type != bindingInfo.type && !validBindingConversion,
                 "The buffer type in the shader (%s) is not compatible with the type in the "
                 "layout (%s).",
-                bindingInfo.type, layoutInfo.buffer.type);
+                bindingInfo.type, bindingLayout.type);
 
-            DAWN_INVALID_IF(layoutInfo.buffer.minBindingSize != 0 &&
-                                bindingInfo.minBindingSize > layoutInfo.buffer.minBindingSize,
+            DAWN_INVALID_IF(bindingLayout.minBindingSize != 0 &&
+                                bindingInfo.minBindingSize > bindingLayout.minBindingSize,
                             "The shader uses more bytes of the buffer (%u) than the layout's "
                             "minBindingSize (%u).",
-                            bindingInfo.minBindingSize, layoutInfo.buffer.minBindingSize);
+                            bindingInfo.minBindingSize, bindingLayout.minBindingSize);
             return {};
         },
         [&](const SamplerBindingInfo& bindingInfo) -> MaybeError {
+            const SamplerBindingLayout& bindingLayout =
+                std::get<SamplerBindingLayout>(layoutInfo.bindingLayout);
             DAWN_INVALID_IF(
-                (layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison) !=
+                (bindingLayout.type == wgpu::SamplerBindingType::Comparison) !=
                     bindingInfo.isComparison,
                 "The sampler type in the shader (comparison: %u) doesn't match the type in "
                 "the layout (comparison: %u).",
                 bindingInfo.isComparison,
-                layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison);
+                bindingLayout.type == wgpu::SamplerBindingType::Comparison);
             return {};
         },
-
-        [&](const ExternalTextureBindingInfo&) -> MaybeError {
+        [](const ExternalTextureBindingInfo&) -> MaybeError {
             DAWN_UNREACHABLE();
             return {};
         });
@@ -1153,21 +1162,17 @@
             layout->GetBindGroupLayout(pair.sampler.group);
         const BindingInfo& samplerInfo =
             samplerBGL->GetBindingInfo(samplerBGL->GetBindingIndex(pair.sampler.binding));
-        if (samplerInfo.sampler.type != wgpu::SamplerBindingType::Filtering) {
+        const SamplerBindingLayout& samplerLayout =
+            std::get<SamplerBindingLayout>(samplerInfo.bindingLayout);
+        if (samplerLayout.type != wgpu::SamplerBindingType::Filtering) {
             continue;
         }
         const BindGroupLayoutInternalBase* textureBGL =
             layout->GetBindGroupLayout(pair.texture.group);
         const BindingInfo& textureInfo =
             textureBGL->GetBindingInfo(textureBGL->GetBindingIndex(pair.texture.binding));
-
-        DAWN_ASSERT(textureInfo.bindingType != BindingInfoType::Buffer &&
-                    textureInfo.bindingType != BindingInfoType::Sampler &&
-                    textureInfo.bindingType != BindingInfoType::StorageTexture);
-
-        if (textureInfo.bindingType != BindingInfoType::Texture) {
-            continue;
-        }
+        const TextureBindingLayout& sampledTextureBindingLayout =
+            std::get<TextureBindingLayout>(textureInfo.bindingLayout);
 
         // Uint/Sint can't be statically used with a sampler, so they any
         // texture bindings reflected must be float or depth textures. If
@@ -1175,12 +1180,12 @@
         // specifies a uint/sint texture binding,
         // |ValidateCompatibilityWithBindGroupLayout| will fail since the
         // sampleType does not match.
-        DAWN_ASSERT(textureInfo.texture.sampleType != wgpu::TextureSampleType::Undefined &&
-                    textureInfo.texture.sampleType != wgpu::TextureSampleType::Uint &&
-                    textureInfo.texture.sampleType != wgpu::TextureSampleType::Sint);
+        DAWN_ASSERT(sampledTextureBindingLayout.sampleType != wgpu::TextureSampleType::Undefined &&
+                    sampledTextureBindingLayout.sampleType != wgpu::TextureSampleType::Uint &&
+                    sampledTextureBindingLayout.sampleType != wgpu::TextureSampleType::Sint);
 
         DAWN_INVALID_IF(
-            textureInfo.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat,
+            sampledTextureBindingLayout.sampleType == wgpu::TextureSampleType::UnfilterableFloat,
             "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, wgpu::TextureSampleType::UnfilterableFloat,
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
index 27323c1..df0c29f 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
@@ -31,6 +31,7 @@
 #include <vector>
 
 #include "dawn/common/Assert.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/Format.h"
 #include "dawn/native/d3d/D3DError.h"
 #include "dawn/native/d3d11/BindGroupD3D11.h"
@@ -172,16 +173,17 @@
                  ++bindingIndex) {
                 const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
 
-                switch (bindingInfo.bindingType) {
-                    case BindingInfoType::Buffer: {
+                DAWN_TRY(MatchVariant(
+                    bindingInfo.bindingLayout,
+                    [&](const BufferBindingLayout& layout) -> MaybeError {
                         BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
                         auto offset = binding.offset;
-                        if (bindingInfo.buffer.hasDynamicOffset) {
+                        if (layout.hasDynamicOffset) {
                             // Dynamic buffers are packed at the front of BindingIndices.
                             offset += dynamicOffsets[bindingIndex];
                         }
 
-                        switch (bindingInfo.buffer.type) {
+                        switch (layout.type) {
                             case wgpu::BufferBindingType::Storage:
                             case kInternalStorageBufferBinding: {
                                 DAWN_ASSERT(IsSubset(
@@ -202,11 +204,10 @@
                                 break;
                             }
                         }
-                        break;
-                    }
-
-                    case BindingInfoType::StorageTexture: {
-                        switch (bindingInfo.storageTexture.access) {
+                        return {};
+                    },
+                    [&](const StorageTextureBindingLayout& layout) -> MaybeError {
+                        switch (layout.access) {
                             case wgpu::StorageTextureAccess::WriteOnly:
                             case wgpu::StorageTextureAccess::ReadWrite: {
                                 ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
@@ -224,14 +225,10 @@
                                 DAWN_UNREACHABLE();
                                 break;
                         }
-                        break;
-                    }
-                    case BindingInfoType::Texture:
-                    case BindingInfoType::ExternalTexture:
-                    case BindingInfoType::Sampler: {
-                        break;
-                    }
-                }
+                        return {};
+                    },
+                    [](const TextureBindingLayout&) -> MaybeError { return {}; },
+                    [](const SamplerBindingLayout&) -> MaybeError { return {}; }));
             }
         }
 
@@ -288,16 +285,17 @@
         const uint32_t bindingSlot = indices[bindingIndex];
         const auto bindingVisibility = bindingInfo.visibility & mVisibleStages;
 
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
+        DAWN_TRY(MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout& layout) -> MaybeError {
                 BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
                 auto offset = binding.offset;
-                if (bindingInfo.buffer.hasDynamicOffset) {
+                if (layout.hasDynamicOffset) {
                     // Dynamic buffers are packed at the front of BindingIndices.
                     offset += dynamicOffsets[bindingIndex];
                 }
 
-                switch (bindingInfo.buffer.type) {
+                switch (layout.type) {
                     case wgpu::BufferBindingType::Uniform: {
                         ToBackend(binding.buffer)->EnsureConstantBufferIsUpdated(mCommandContext);
                         ID3D11Buffer* d3d11Buffer =
@@ -366,10 +364,9 @@
                     case wgpu::BufferBindingType::Undefined:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::Sampler: {
+                return {};
+            },
+            [&](const SamplerBindingLayout&) -> MaybeError {
                 Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                 ID3D11SamplerState* d3d11SamplerState = sampler->GetD3D11SamplerState();
                 if (bindingVisibility & wgpu::ShaderStage::Vertex) {
@@ -381,10 +378,9 @@
                 if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext->CSSetSamplers(bindingSlot, 1, &d3d11SamplerState);
                 }
-                break;
-            }
-
-            case BindingInfoType::Texture: {
+                return {};
+            },
+            [&](const TextureBindingLayout&) -> MaybeError {
                 TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                 ComPtr<ID3D11ShaderResourceView> srv;
                 // For sampling from stencil, we have to use an internal mirror 'R8Uint' texture.
@@ -403,12 +399,11 @@
                 if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext->CSSetShaderResources(bindingSlot, 1, srv.GetAddressOf());
                 }
-                break;
-            }
-
-            case BindingInfoType::StorageTexture: {
+                return {};
+            },
+            [&](const StorageTextureBindingLayout& layout) -> MaybeError {
                 TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
-                switch (bindingInfo.storageTexture.access) {
+                switch (layout.access) {
                     case wgpu::StorageTextureAccess::WriteOnly:
                     case wgpu::StorageTextureAccess::ReadWrite: {
                         ID3D11UnorderedAccessView* d3d11UAV = nullptr;
@@ -436,13 +431,8 @@
                     default:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::ExternalTexture: {
-                return DAWN_UNIMPLEMENTED_ERROR("External textures are not supported");
-            }
-        }
+                return {};
+            }));
     }
     return {};
 }
@@ -459,9 +449,10 @@
         const uint32_t bindingSlot = indices[bindingIndex];
         const auto bindingVisibility = bindingInfo.visibility & mVisibleStages;
 
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
-                switch (bindingInfo.buffer.type) {
+        MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout& layout) {
+                switch (layout.type) {
                     case wgpu::BufferBindingType::Uniform: {
                         ID3D11Buffer* nullBuffer = nullptr;
                         if (bindingVisibility & wgpu::ShaderStage::Vertex) {
@@ -511,10 +502,8 @@
                     case wgpu::BufferBindingType::Undefined:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::Sampler: {
+            },
+            [&](const SamplerBindingLayout&) {
                 ID3D11SamplerState* nullSampler = nullptr;
                 if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext->VSSetSamplers(bindingSlot, 1, &nullSampler);
@@ -525,10 +514,8 @@
                 if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext->CSSetSamplers(bindingSlot, 1, &nullSampler);
                 }
-                break;
-            }
-
-            case BindingInfoType::Texture: {
+            },
+            [&](const TextureBindingLayout&) {
                 ID3D11ShaderResourceView* nullSRV = nullptr;
                 if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext->VSSetShaderResources(bindingSlot, 1, &nullSRV);
@@ -539,11 +526,9 @@
                 if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext->CSSetShaderResources(bindingSlot, 1, &nullSRV);
                 }
-                break;
-            }
-
-            case BindingInfoType::StorageTexture: {
-                switch (bindingInfo.storageTexture.access) {
+            },
+            [&](const StorageTextureBindingLayout& layout) {
+                switch (layout.access) {
                     case wgpu::StorageTextureAccess::WriteOnly:
                     case wgpu::StorageTextureAccess::ReadWrite: {
                         ID3D11UnorderedAccessView* nullUAV = nullptr;
@@ -574,14 +559,7 @@
                     default:
                         DAWN_UNREACHABLE();
                 }
-                break;
-            }
-
-            case BindingInfoType::ExternalTexture: {
-                DAWN_UNREACHABLE();
-                break;
-            }
-        }
+            });
     }
 }
 
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
index 84534c6..b0915be 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
@@ -28,6 +28,7 @@
 #include "dawn/native/d3d11/PipelineLayoutD3D11.h"
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/d3d11/DeviceD3D11.h"
 
@@ -61,9 +62,10 @@
 
         for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
-            switch (bindingInfo.bindingType) {
-                case BindingInfoType::Buffer:
-                    switch (bindingInfo.buffer.type) {
+            MatchVariant(
+                bindingInfo.bindingLayout,
+                [&](const BufferBindingLayout& layout) {
+                    switch (layout.type) {
                         case wgpu::BufferBindingType::Uniform:
                             mIndexInfo[group][bindingIndex] = constantBufferIndex++;
                             break;
@@ -78,19 +80,15 @@
                         case wgpu::BufferBindingType::Undefined:
                             DAWN_UNREACHABLE();
                     }
-                    break;
-
-                case BindingInfoType::Sampler:
+                },
+                [&](const SamplerBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = samplerIndex++;
-                    break;
-
-                case BindingInfoType::Texture:
-                case BindingInfoType::ExternalTexture:
+                },
+                [&](const TextureBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = shaderResourceViewIndex++;
-                    break;
-
-                case BindingInfoType::StorageTexture:
-                    switch (bindingInfo.storageTexture.access) {
+                },
+                [&](const StorageTextureBindingLayout& layout) {
+                    switch (layout.access) {
                         case wgpu::StorageTextureAccess::ReadWrite:
                         case wgpu::StorageTextureAccess::WriteOnly:
                             mIndexInfo[group][bindingIndex] = --unorderedAccessViewIndex;
@@ -102,8 +100,7 @@
                         case wgpu::StorageTextureAccess::Undefined:
                             DAWN_UNREACHABLE();
                     }
-                    break;
-            }
+                });
         }
     }
     mUnusedUAVBindingCount = unorderedAccessViewIndex;
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.cpp b/src/dawn/native/d3d12/BindGroupD3D12.cpp
index 7b27cbd..7298e6b 100644
--- a/src/dawn/native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupD3D12.cpp
@@ -30,6 +30,7 @@
 #include <utility>
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/ExternalTexture.h"
 #include "dawn/native/Queue.h"
 #include "dawn/native/d3d12/BindGroupLayoutD3D12.h"
@@ -71,8 +72,9 @@
 
         // Increment size does not need to be stored and is only used to get a handle
         // local to the allocation with OffsetFrom().
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
+        MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout& layout) {
                 BufferBinding binding = GetBindingAsBufferBinding(bindingIndex);
 
                 ID3D12Resource* resource = ToBackend(binding.buffer)->GetD3D12Resource();
@@ -80,10 +82,10 @@
                     // The Buffer was destroyed. Skip creating buffer views since there is no
                     // resource. This bind group won't be used as it is an error to submit a
                     // command buffer that references destroyed resources.
-                    continue;
+                    return;
                 }
 
-                switch (bindingInfo.buffer.type) {
+                switch (layout.type) {
                     case wgpu::BufferBindingType::Uniform: {
                         D3D12_CONSTANT_BUFFER_VIEW_DESC desc;
                         desc.SizeInBytes =
@@ -141,11 +143,8 @@
                     case wgpu::BufferBindingType::Undefined:
                         DAWN_UNREACHABLE();
                 }
-
-                break;
-            }
-
-            case BindingInfoType::Texture: {
+            },
+            [&](const TextureBindingLayout&) {
                 auto* view = ToBackend(GetBindingAsTextureView(bindingIndex));
                 auto& srv = view->GetSRVDescriptor();
 
@@ -154,17 +153,15 @@
                     // The Texture was destroyed. Skip creating the SRV since there is no
                     // resource. This bind group won't be used as it is an error to submit a
                     // command buffer that references destroyed resources.
-                    continue;
+                    return;
                 }
 
                 d3d12Device->CreateShaderResourceView(
                     resource, &srv,
                     viewAllocation.OffsetFrom(viewSizeIncrement,
                                               descriptorHeapOffsets[bindingIndex]));
-                break;
-            }
-
-            case BindingInfoType::StorageTexture: {
+            },
+            [&](const StorageTextureBindingLayout& layout) {
                 TextureView* view = ToBackend(GetBindingAsTextureView(bindingIndex));
 
                 ID3D12Resource* resource = ToBackend(view->GetTexture())->GetD3D12Resource();
@@ -172,10 +169,10 @@
                     // The Texture was destroyed. Skip creating the SRV/UAV since there is no
                     // resource. This bind group won't be used as it is an error to submit a
                     // command buffer that references destroyed resources.
-                    continue;
+                    return;
                 }
 
-                switch (bindingInfo.storageTexture.access) {
+                switch (layout.access) {
                     case wgpu::StorageTextureAccess::WriteOnly:
                     case wgpu::StorageTextureAccess::ReadWrite: {
                         D3D12_UNORDERED_ACCESS_VIEW_DESC uav = view->GetUAVDescriptor();
@@ -196,19 +193,9 @@
                     case wgpu::StorageTextureAccess::Undefined:
                         DAWN_UNREACHABLE();
                 }
-
-                break;
-            }
-
-            case BindingInfoType::ExternalTexture: {
-                DAWN_UNREACHABLE();
-            }
-
-            case BindingInfoType::Sampler: {
-                // No-op as samplers will be later initialized by CreateSamplers().
-                break;
-            }
-        }
+            },
+            // No-op as samplers will be later initialized by CreateSamplers().
+            [](const SamplerBindingLayout&) {});
     }
 
     // Loop through the dynamic storage buffers and build a flat map from the index of the
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
index af67683..ed02fde 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
@@ -30,6 +30,7 @@
 #include <utility>
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/d3d12/DeviceD3D12.h"
 #include "dawn/native/d3d12/SamplerHeapCacheD3D12.h"
 #include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h"
@@ -37,9 +38,10 @@
 namespace dawn::native::d3d12 {
 namespace {
 D3D12_DESCRIPTOR_RANGE_TYPE WGPUBindingInfoToDescriptorRangeType(const BindingInfo& bindingInfo) {
-    switch (bindingInfo.bindingType) {
-        case BindingInfoType::Buffer:
-            switch (bindingInfo.buffer.type) {
+    return MatchVariant(
+        bindingInfo.bindingLayout,
+        [](const BufferBindingLayout& layout) -> D3D12_DESCRIPTOR_RANGE_TYPE {
+            switch (layout.type) {
                 case wgpu::BufferBindingType::Uniform:
                     return D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
                 case wgpu::BufferBindingType::Storage:
@@ -50,16 +52,15 @@
                 case wgpu::BufferBindingType::Undefined:
                     DAWN_UNREACHABLE();
             }
-
-        case BindingInfoType::Sampler:
+        },
+        [](const SamplerBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
             return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
-
-        case BindingInfoType::Texture:
-        case BindingInfoType::ExternalTexture:
+        },
+        [](const TextureBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
             return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
-
-        case BindingInfoType::StorageTexture:
-            switch (bindingInfo.storageTexture.access) {
+        },
+        [](const StorageTextureBindingLayout& layout) -> D3D12_DESCRIPTOR_RANGE_TYPE {
+            switch (layout.access) {
                 case wgpu::StorageTextureAccess::WriteOnly:
                 case wgpu::StorageTextureAccess::ReadWrite:
                     return D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
@@ -68,7 +69,7 @@
                 case wgpu::StorageTextureAccess::Undefined:
                     DAWN_UNREACHABLE();
             }
-    }
+        });
 }
 }  // anonymous namespace
 
@@ -97,7 +98,8 @@
         if (bindingIndex < GetDynamicBufferCount()) {
             continue;
         }
-        DAWN_ASSERT(!bindingInfo.buffer.hasDynamicOffset);
+        DAWN_ASSERT(!std::holds_alternative<BufferBindingLayout>(bindingInfo.bindingLayout) ||
+                    !std::get<BufferBindingLayout>(bindingInfo.bindingLayout).hasDynamicOffset);
 
         mDescriptorHeapOffsets[bindingIndex] =
             descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
@@ -116,36 +118,32 @@
         // the descriptor table is set on a command list (during recording), and the descriptors
         // cannot be changed until the command list has finished executing for the last time, so we
         // don't need to set DESCRIPTORS_VOLATILE for any binding types.
-        switch (bindingInfo.bindingType) {
-            // Sampler descriptor ranges don't support DATA_* flags at all since samplers do not
-            // point to data.
-            case BindingInfoType::Sampler:
-                range.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;
-                break;
+        range.Flags = MatchVariant(
+            bindingInfo.bindingLayout,
+            [](const SamplerBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                // Sampler descriptor ranges don't support DATA_* flags at all since samplers do not
+                // point to data.
+                return D3D12_DESCRIPTOR_RANGE_FLAG_NONE;
+            },
+            [](const BufferBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                // In Dawn it's allowed to do state transitions on the buffers or textures after
+                // binding
+                // them on the current command list, which indicates a change to its data (or
+                // possibly resource metadata), so we cannot bind them as DATA_STATIC. We cannot
+                // bind them as DATA_STATIC_WHILE_SET_AT_EXECUTE either because it is required to be
+                // rebound to the command list before the next (this) Draw/Dispatch call, while
+                // currently we may not rebind these resources if the current bind group is not
+                // changed.
+                return D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS |
+                       D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
+            },
+            [](const TextureBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                return D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
+            },
+            [](const StorageTextureBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                return D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
+            });
 
-            // In Dawn it's allowed to do state transitions on the buffers or textures after binding
-            // them on the current command list, which indicates a change to its data (or possibly
-            // resource metadata), so we cannot bind them as DATA_STATIC.
-            // We cannot bind them as DATA_STATIC_WHILE_SET_AT_EXECUTE either because it is required
-            // to be rebound to the command list before the next (this) Draw/Dispatch call, while
-            // currently we may not rebind these resources if the current bind group is not changed.
-            case BindingInfoType::Buffer:
-                range.Flags =
-                    D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS |
-                    D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
-                break;
-            case BindingInfoType::Texture:
-            case BindingInfoType::StorageTexture:
-                range.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
-                break;
-
-            // ExternalTexture bindings are decayed in the frontend and backends shouldn't need to
-            // handle them.
-            case BindingInfoType::ExternalTexture:
-            default:
-                DAWN_UNREACHABLE();
-                break;
-        }
         std::vector<D3D12_DESCRIPTOR_RANGE1>& descriptorRanges =
             descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER ? mSamplerDescriptorRanges
                                                                        : mCbvUavSrvDescriptorRanges;
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 0842470..90b329c 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -524,8 +524,7 @@
                 D3D12_GPU_VIRTUAL_ADDRESS bufferLocation =
                     ToBackend(binding.buffer)->GetVA() + offset;
 
-                DAWN_ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer);
-                switch (bindingInfo.buffer.type) {
+                switch (std::get<BufferBindingLayout>(bindingInfo.bindingLayout).type) {
                     case wgpu::BufferBindingType::Uniform:
                         if (mInCompute) {
                             commandList->SetComputeRootConstantBufferView(parameterIndex,
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
index c56e294..7aa0cff 100644
--- a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
@@ -249,7 +249,8 @@
             mDynamicRootParameterIndices[group][dynamicBindingIndex] = rootParameters.size();
 
             // Set parameter types according to bind group layout descriptor.
-            rootParameter.ParameterType = RootParameterType(bindingInfo.buffer.type);
+            rootParameter.ParameterType =
+                RootParameterType(std::get<BufferBindingLayout>(bindingInfo.bindingLayout).type);
 
             // Set visibilities according to bind group layout descriptor.
             rootParameter.ShaderVisibility = ShaderVisibilityType(bindingInfo.visibility);
@@ -424,7 +425,9 @@
 uint32_t PipelineLayout::GetDynamicRootParameterIndex(BindGroupIndex group,
                                                       BindingIndex bindingIndex) const {
     DAWN_ASSERT(group < kMaxBindGroupsTyped);
-    DAWN_ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).buffer.hasDynamicOffset);
+    DAWN_ASSERT(std::get<BufferBindingLayout>(
+                    GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).bindingLayout)
+                    .hasDynamicOffset);
     DAWN_ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).visibility !=
                 wgpu::ShaderStage::None);
     return mDynamicRootParameterIndices[group][bindingIndex];
diff --git a/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp b/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
index 0351bbc..da7d4e2 100644
--- a/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
+++ b/src/dawn/native/d3d12/SamplerHeapCacheD3D12.cpp
@@ -120,7 +120,7 @@
     for (BindingIndex bindingIndex = bgl->GetDynamicBufferCount();
          bindingIndex < bgl->GetBindingCount(); ++bindingIndex) {
         const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
-        if (bindingInfo.bindingType == BindingInfoType::Sampler) {
+        if (std::holds_alternative<SamplerBindingLayout>(bindingInfo.bindingLayout)) {
             samplers.push_back(ToBackend(group->GetBindingAsSampler(bindingIndex)));
         }
     }
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
index 6bf7ba7..431efd2 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
@@ -211,15 +211,17 @@
                 continue;
             }
 
+            const auto& bindingLayout =
+                std::get<BufferBindingLayout>(bgl->GetBindingInfo(bindingIndex).bindingLayout);
+
             // Declaring a read-only storage buffer in HLSL but specifying a storage
             // buffer in the BGL produces the wrong output. Force read-only storage
             // buffer bindings to be treated as UAV instead of SRV. Internal storage
             // buffer is a storage buffer used in the internal pipeline.
             const bool forceStorageBufferAsUAV =
                 (bufferBindingInfo->type == wgpu::BufferBindingType::ReadOnlyStorage &&
-                 (bgl->GetBindingInfo(bindingIndex).buffer.type ==
-                      wgpu::BufferBindingType::Storage ||
-                  bgl->GetBindingInfo(bindingIndex).buffer.type == kInternalStorageBufferBinding));
+                 (bindingLayout.type == wgpu::BufferBindingType::Storage ||
+                  bindingLayout.type == kInternalStorageBufferBinding));
             if (forceStorageBufferAsUAV) {
                 accessControls.emplace(srcBindingPoint, tint::core::Access::kReadWrite);
             }
@@ -250,7 +252,7 @@
             // }
             if ((bufferBindingInfo->type == wgpu::BufferBindingType::Storage ||
                  bufferBindingInfo->type == wgpu::BufferBindingType::ReadOnlyStorage) &&
-                !bgl->GetBindingInfo(bindingIndex).buffer.hasDynamicOffset) {
+                !bindingLayout.hasDynamicOffset) {
                 req.hlsl.tintOptions.binding_points_ignored_in_robustness_transform.emplace_back(
                     srcBindingPoint);
             }
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index d4da77f..ce703a7 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -27,6 +27,7 @@
 
 #include "dawn/native/metal/CommandBufferMTL.h"
 
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroupTracker.h"
 #include "dawn/native/CommandEncoder.h"
 #include "dawn/native/Commands.h"
@@ -570,8 +571,9 @@
                     SingleShaderStage::Compute)[index][bindingIndex];
             }
 
-            switch (bindingInfo.bindingType) {
-                case BindingInfoType::Buffer: {
+            MatchVariant(
+                bindingInfo.bindingLayout,
+                [&](const BufferBindingLayout& layout) {
                     const BufferBinding& binding = group->GetBindingAsBufferBinding(bindingIndex);
                     ToBackend(binding.buffer)->TrackUsage();
                     const id<MTLBuffer> buffer = ToBackend(binding.buffer)->GetMTLBuffer();
@@ -579,7 +581,7 @@
 
                     // TODO(crbug.com/dawn/854): Record bound buffer status to use
                     // setBufferOffset to achieve better performance.
-                    if (bindingInfo.buffer.hasDynamicOffset) {
+                    if (layout.hasDynamicOffset) {
                         // Dynamic buffers are packed at the front of BindingIndices.
                         offset += dynamicOffsets[bindingIndex];
                     }
@@ -606,11 +608,8 @@
                                     offsets:&offset
                                   withRange:NSMakeRange(computeIndex, 1)];
                     }
-
-                    break;
-                }
-
-                case BindingInfoType::Sampler: {
+                },
+                [&](const SamplerBindingLayout&) {
                     auto sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                     if (hasVertStage) {
                         [render setVertexSamplerState:sampler->GetMTLSamplerState()
@@ -624,11 +623,8 @@
                         [compute setSamplerState:sampler->GetMTLSamplerState()
                                          atIndex:computeIndex];
                     }
-                    break;
-                }
-
-                case BindingInfoType::Texture:
-                case BindingInfoType::StorageTexture: {
+                },
+                [&](const TextureBindingLayout&) {
                     auto textureView = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                     if (hasVertStage) {
                         [render setVertexTexture:textureView->GetMTLTexture() atIndex:vertIndex];
@@ -639,12 +635,19 @@
                     if (hasComputeStage) {
                         [compute setTexture:textureView->GetMTLTexture() atIndex:computeIndex];
                     }
-                    break;
-                }
-
-                case BindingInfoType::ExternalTexture:
-                    DAWN_UNREACHABLE();
-            }
+                },
+                [&](const StorageTextureBindingLayout&) {
+                    auto textureView = ToBackend(group->GetBindingAsTextureView(bindingIndex));
+                    if (hasVertStage) {
+                        [render setVertexTexture:textureView->GetMTLTexture() atIndex:vertIndex];
+                    }
+                    if (hasFragStage) {
+                        [render setFragmentTexture:textureView->GetMTLTexture() atIndex:fragIndex];
+                    }
+                    if (hasComputeStage) {
+                        [compute setTexture:textureView->GetMTLTexture() atIndex:computeIndex];
+                    }
+                });
         }
     }
 
diff --git a/src/dawn/native/metal/PipelineLayoutMTL.mm b/src/dawn/native/metal/PipelineLayoutMTL.mm
index ab78922..6bfc9dd 100644
--- a/src/dawn/native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn/native/metal/PipelineLayoutMTL.mm
@@ -28,6 +28,7 @@
 #include "dawn/native/metal/PipelineLayoutMTL.h"
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/metal/DeviceMTL.h"
 
@@ -60,24 +61,24 @@
                     continue;
                 }
 
-                switch (bindingInfo.bindingType) {
-                    case BindingInfoType::Buffer:
+                MatchVariant(
+                    bindingInfo.bindingLayout,
+                    [&](const BufferBindingLayout&) {
                         mIndexInfo[stage][group][bindingIndex] = bufferIndex;
                         bufferIndex++;
-                        break;
-
-                    case BindingInfoType::Sampler:
+                    },
+                    [&](const SamplerBindingLayout&) {
                         mIndexInfo[stage][group][bindingIndex] = samplerIndex;
                         samplerIndex++;
-                        break;
-
-                    case BindingInfoType::Texture:
-                    case BindingInfoType::StorageTexture:
-                    case BindingInfoType::ExternalTexture:
+                    },
+                    [&](const TextureBindingLayout&) {
                         mIndexInfo[stage][group][bindingIndex] = textureIndex;
                         textureIndex++;
-                        break;
-                }
+                    },
+                    [&](const StorageTextureBindingLayout&) {
+                        mIndexInfo[stage][group][bindingIndex] = textureIndex;
+                        textureIndex++;
+                    });
             }
         }
 
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 578c13b..d4aa7aa 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -32,6 +32,7 @@
 #include <utility>
 #include <vector>
 
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroup.h"
 #include "dawn/native/BindGroupTracker.h"
 #include "dawn/native/CommandEncoder.h"
@@ -269,7 +270,7 @@
              ++bindingIndex) {
             const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
 
-            if (bindingInfo.bindingType == BindingInfoType::Texture) {
+            if (std::holds_alternative<TextureBindingLayout>(bindingInfo.bindingLayout)) {
                 TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                 view->CopyIfNeeded();
             }
@@ -278,21 +279,21 @@
         for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount();
              ++bindingIndex) {
             const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
-
-            switch (bindingInfo.bindingType) {
-                case BindingInfoType::Buffer: {
+            MatchVariant(
+                bindingInfo.bindingLayout,
+                [&](const BufferBindingLayout& layout) {
                     BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
                     GLuint buffer = ToBackend(binding.buffer)->GetHandle();
                     GLuint index = indices[bindingIndex];
                     GLuint offset = binding.offset;
 
-                    if (bindingInfo.buffer.hasDynamicOffset) {
+                    if (layout.hasDynamicOffset) {
                         // Dynamic buffers are packed at the front of BindingIndices.
                         offset += dynamicOffsets[bindingIndex];
                     }
 
                     GLenum target;
-                    switch (bindingInfo.buffer.type) {
+                    switch (layout.type) {
                         case wgpu::BufferBindingType::Uniform:
                             target = GL_UNIFORM_BUFFER;
                             break;
@@ -306,10 +307,8 @@
                     }
 
                     gl.BindBufferRange(target, index, buffer, offset, binding.size);
-                    break;
-                }
-
-                case BindingInfoType::Sampler: {
+                },
+                [&](const SamplerBindingLayout&) {
                     Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                     GLuint samplerIndex = indices[bindingIndex];
 
@@ -323,10 +322,8 @@
                             gl.BindSampler(unit.unit, sampler->GetNonFilteringHandle());
                         }
                     }
-                    break;
-                }
-
-                case BindingInfoType::Texture: {
+                },
+                [&](const TextureBindingLayout&) {
                     TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                     GLuint handle = view->GetHandle();
                     GLenum target = view->GetGLTarget();
@@ -365,18 +362,15 @@
                     // Some texture builtin function data needs emulation to update into the
                     // internal uniform buffer.
                     UpdateTextureBuiltinsUniformData(gl, view, groupIndex, bindingIndex);
-
-                    break;
-                }
-
-                case BindingInfoType::StorageTexture: {
+                },
+                [&](const StorageTextureBindingLayout& layout) {
                     TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                     Texture* texture = ToBackend(view->GetTexture());
                     GLuint handle = texture->GetHandle();
                     GLuint imageIndex = indices[bindingIndex];
 
                     GLenum access;
-                    switch (bindingInfo.storageTexture.access) {
+                    switch (layout.access) {
                         case wgpu::StorageTextureAccess::WriteOnly:
                             access = GL_WRITE_ONLY;
                             break;
@@ -405,13 +399,7 @@
                                         view->GetBaseArrayLayer(), access,
                                         texture->GetGLFormat().internalFormat);
                     texture->Touch();
-                    break;
-                }
-
-                case BindingInfoType::ExternalTexture:
-                    DAWN_UNREACHABLE();
-                    break;
-            }
+                });
         }
     }
 
diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp
index f59c41e..6c5d12b 100644
--- a/src/dawn/native/opengl/PipelineGL.cpp
+++ b/src/dawn/native/opengl/PipelineGL.cpp
@@ -147,7 +147,8 @@
             GLuint textureIndex = indices[combined.textureLocation.group][bindingIndex];
             mUnitsForTextures[textureIndex].push_back(textureUnit);
 
-            shouldUseFiltering = bgl->GetBindingInfo(bindingIndex).texture.sampleType ==
+            const auto& bindingLayout = bgl->GetBindingInfo(bindingIndex).bindingLayout;
+            shouldUseFiltering = std::get<TextureBindingLayout>(bindingLayout).sampleType ==
                                  wgpu::TextureSampleType::Float;
         }
         {
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.cpp b/src/dawn/native/opengl/PipelineLayoutGL.cpp
index 57bcf00..4b44aa6 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn/native/opengl/PipelineLayoutGL.cpp
@@ -28,6 +28,7 @@
 #include "dawn/native/opengl/PipelineLayoutGL.h"
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/BindGroupLayoutInternal.h"
 #include "dawn/native/opengl/DeviceGL.h"
 
@@ -48,9 +49,10 @@
 
         for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex);
-            switch (bindingInfo.bindingType) {
-                case BindingInfoType::Buffer:
-                    switch (bindingInfo.buffer.type) {
+            MatchVariant(
+                bindingInfo.bindingLayout,
+                [&](const BufferBindingLayout& layout) {
+                    switch (layout.type) {
                         case wgpu::BufferBindingType::Uniform:
                             mIndexInfo[group][bindingIndex] = uboIndex;
                             uboIndex++;
@@ -64,24 +66,19 @@
                         case wgpu::BufferBindingType::Undefined:
                             DAWN_UNREACHABLE();
                     }
-                    break;
-
-                case BindingInfoType::Sampler:
+                },
+                [&](const SamplerBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = samplerIndex;
                     samplerIndex++;
-                    break;
-
-                case BindingInfoType::Texture:
-                case BindingInfoType::ExternalTexture:
+                },
+                [&](const TextureBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = sampledTextureIndex;
                     sampledTextureIndex++;
-                    break;
-
-                case BindingInfoType::StorageTexture:
+                },
+                [&](const StorageTextureBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = storageTextureIndex;
                     storageTextureIndex++;
-                    break;
-            }
+                });
         }
     }
 
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
index 0a79620..3f78a37 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
@@ -31,6 +31,7 @@
 
 #include "absl/container/flat_hash_map.h"
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/common/ityp_vector.h"
 #include "dawn/native/CacheKey.h"
 #include "dawn/native/vulkan/DescriptorSetAllocator.h"
@@ -62,33 +63,30 @@
 }  // anonymous namespace
 
 VkDescriptorType VulkanDescriptorType(const BindingInfo& bindingInfo) {
-    switch (bindingInfo.bindingType) {
-        case BindingInfoType::Buffer:
-            switch (bindingInfo.buffer.type) {
+    return MatchVariant(
+        bindingInfo.bindingLayout,
+        [](const BufferBindingLayout& layout) -> VkDescriptorType {
+            switch (layout.type) {
                 case wgpu::BufferBindingType::Uniform:
-                    if (bindingInfo.buffer.hasDynamicOffset) {
+                    if (layout.hasDynamicOffset) {
                         return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
                     }
                     return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
                 case wgpu::BufferBindingType::Storage:
                 case kInternalStorageBufferBinding:
                 case wgpu::BufferBindingType::ReadOnlyStorage:
-                    if (bindingInfo.buffer.hasDynamicOffset) {
+                    if (layout.hasDynamicOffset) {
                         return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
                     }
                     return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
                 case wgpu::BufferBindingType::Undefined:
                     DAWN_UNREACHABLE();
+                    return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
             }
-        case BindingInfoType::Sampler:
-            return VK_DESCRIPTOR_TYPE_SAMPLER;
-        case BindingInfoType::Texture:
-        case BindingInfoType::ExternalTexture:
-            return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-        case BindingInfoType::StorageTexture:
-            return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
-    }
-    DAWN_UNREACHABLE();
+        },
+        [](const SamplerBindingLayout&) { return VK_DESCRIPTOR_TYPE_SAMPLER; },
+        [](const TextureBindingLayout&) { return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; },
+        [](const StorageTextureBindingLayout&) { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; });
 }
 
 // static
diff --git a/src/dawn/native/vulkan/BindGroupVk.cpp b/src/dawn/native/vulkan/BindGroupVk.cpp
index c619e88..fa40523 100644
--- a/src/dawn/native/vulkan/BindGroupVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupVk.cpp
@@ -28,6 +28,7 @@
 #include "dawn/native/vulkan/BindGroupVk.h"
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/common/MatchVariant.h"
 #include "dawn/common/ityp_stack_vec.h"
 #include "dawn/native/ExternalTexture.h"
 #include "dawn/native/vulkan/BindGroupLayoutVk.h"
@@ -63,7 +64,10 @@
         bindingCount);
 
     uint32_t numWrites = 0;
-    for (const auto [_, bindingIndex] : GetLayout()->GetBindingMap()) {
+    for (const auto& bindingItem : GetLayout()->GetBindingMap()) {
+        // We cannot use structured binding here because lambda expressions can only capture
+        // variables, while structured binding doesn't introduce variables.
+        BindingIndex bindingIndex = bindingItem.second;
         const BindingInfo& bindingInfo = GetLayout()->GetBindingInfo(bindingIndex);
 
         auto& write = writes[numWrites];
@@ -75,8 +79,9 @@
         write.descriptorCount = 1;
         write.descriptorType = VulkanDescriptorType(bindingInfo);
 
-        switch (bindingInfo.bindingType) {
-            case BindingInfoType::Buffer: {
+        bool isValidDescriptorSet = MatchVariant(
+            bindingInfo.bindingLayout,
+            [&](const BufferBindingLayout&) -> bool {
                 BufferBinding binding = GetBindingAsBufferBinding(bindingIndex);
 
                 VkBuffer handle = ToBackend(binding.buffer)->GetHandle();
@@ -85,23 +90,21 @@
                     // a Vulkan Validation Layers error. This bind group won't be used as it
                     // is an error to submit a command buffer that references destroyed
                     // resources.
-                    continue;
+                    return false;
                 }
                 writeBufferInfo[numWrites].buffer = handle;
                 writeBufferInfo[numWrites].offset = binding.offset;
                 writeBufferInfo[numWrites].range = binding.size;
                 write.pBufferInfo = &writeBufferInfo[numWrites];
-                break;
-            }
-
-            case BindingInfoType::Sampler: {
+                return true;
+            },
+            [&](const SamplerBindingLayout&) -> bool {
                 Sampler* sampler = ToBackend(GetBindingAsSampler(bindingIndex));
                 writeImageInfo[numWrites].sampler = sampler->GetHandle();
                 write.pImageInfo = &writeImageInfo[numWrites];
-                break;
-            }
-
-            case BindingInfoType::Texture: {
+                return true;
+            },
+            [&](const TextureBindingLayout&) -> bool {
                 TextureView* view = ToBackend(GetBindingAsTextureView(bindingIndex));
 
                 VkImageView handle = view->GetHandle();
@@ -111,17 +114,16 @@
                     // a Vulkan Validation Layers error. This bind group won't be used as it
                     // is an error to submit a command buffer that references destroyed
                     // resources.
-                    continue;
+                    return false;
                 }
                 writeImageInfo[numWrites].imageView = handle;
                 writeImageInfo[numWrites].imageLayout = VulkanImageLayout(
                     view->GetTexture()->GetFormat(), wgpu::TextureUsage::TextureBinding);
 
                 write.pImageInfo = &writeImageInfo[numWrites];
-                break;
-            }
-
-            case BindingInfoType::StorageTexture: {
+                return true;
+            },
+            [&](const StorageTextureBindingLayout&) -> bool {
                 TextureView* view = ToBackend(GetBindingAsTextureView(bindingIndex));
 
                 VkImageView handle = VK_NULL_HANDLE;
@@ -136,21 +138,18 @@
                     // a Vulkan Validation Layers error. This bind group won't be used as it
                     // is an error to submit a command buffer that references destroyed
                     // resources.
-                    continue;
+                    return false;
                 }
                 writeImageInfo[numWrites].imageView = handle;
                 writeImageInfo[numWrites].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
 
                 write.pImageInfo = &writeImageInfo[numWrites];
-                break;
-            }
+                return true;
+            });
 
-            case BindingInfoType::ExternalTexture:
-                DAWN_UNREACHABLE();
-                break;
+        if (isValidDescriptorSet) {
+            numWrites++;
         }
-
-        numWrites++;
     }
 
     // TODO(crbug.com/dawn/855): Batch these updates
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 68f5cf1..95e2cc3 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -30,6 +30,7 @@
 #include <string>
 #include <vector>
 
+#include "dawn/common/MatchVariant.h"
 #include "dawn/native/AttachmentState.h"
 #include "dawn/native/BindingInfo.h"
 #include "dawn/native/Device.h"
@@ -115,26 +116,24 @@
     absl::FormatSink* s) {
     static const auto* const fmt =
         new absl::ParsedFormat<'u', 's', 's', 's'>("{ binding: %u, visibility: %s, %s: %s }");
-    switch (value.bindingType) {
-        case BindingInfoType::Buffer:
+    MatchVariant(
+        value.bindingLayout,
+        [&](const BufferBindingLayout& layout) {
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
-                                      value.bindingType, value.buffer));
-            break;
-        case BindingInfoType::Sampler:
+                                      BindingInfoType::Buffer, layout));
+        },
+        [&](const SamplerBindingLayout& layout) {
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
-                                      value.bindingType, value.sampler));
-            break;
-        case BindingInfoType::Texture:
+                                      BindingInfoType::Sampler, layout));
+        },
+        [&](const TextureBindingLayout& layout) {
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
-                                      value.bindingType, value.texture));
-            break;
-        case BindingInfoType::StorageTexture:
+                                      BindingInfoType::Texture, layout));
+        },
+        [&](const StorageTextureBindingLayout& layout) {
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
-                                      value.bindingType, value.storageTexture));
-            break;
-        case BindingInfoType::ExternalTexture:
-            break;
-    }
+                                      BindingInfoType::StorageTexture, layout));
+        });
     return {true};
 }