[dawn][native] Introduce new `TexelBuffer` binding

* Extended binding info, shader modules, and pipeline layout tracking
  for texel buffers.
* Stub backend handling across D3D, Metal, OpenGL and Vulkan.

Bug: 382544164
Change-Id: I0c32ac0e848ddacfb2cf477e636e92c95aecd5c1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/256995
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Diego Rodrigues <diejorarr@gmail.com>
Commit-Queue: Diego Rodrigues <diejorarr@gmail.com>
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 6c7d65e..3075b45 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -47,6 +47,7 @@
 #include "dawn/native/ObjectBase.h"
 #include "dawn/native/ObjectType_autogen.h"
 #include "dawn/native/Sampler.h"
+#include "dawn/native/TexelBufferView.h"
 #include "dawn/native/Texture.h"
 #include "dawn/native/utils/WGPUHelpers.h"
 
@@ -315,6 +316,32 @@
     return {};
 }
 
+MaybeError ValidateTexelBufferBinding(DeviceBase* device,
+                                      const BindGroupEntry& entry,
+                                      const TexelBufferBindingEntry* texelBufferBindingEntry,
+                                      const TexelBufferBindingInfo& layout,
+                                      UsageValidationMode mode) {
+    DAWN_INVALID_IF(
+        entry.buffer != nullptr || entry.sampler != nullptr || entry.textureView != nullptr,
+        "Expected only texelBufferView to be set for binding entry.");
+
+    DAWN_TRY(device->ValidateObject(texelBufferBindingEntry->texelBufferView));
+
+    BufferBase* buffer = texelBufferBindingEntry->texelBufferView->GetBuffer();
+    DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::TexelBuffer));
+
+    DAWN_INVALID_IF(texelBufferBindingEntry->texelBufferView->GetFormat() != layout.format,
+                    "Format (%s) of %s expected to be (%s).",
+                    texelBufferBindingEntry->texelBufferView->GetFormat(),
+                    texelBufferBindingEntry->texelBufferView, layout.format);
+
+    if (layout.access == wgpu::TexelBufferAccess::ReadWrite) {
+        DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::Storage));
+    }
+
+    return {};
+}
+
 MaybeError ValidateSamplerBinding(const DeviceBase* device,
                                   const BindGroupEntry& entry,
                                   const SamplerBindingInfo& layout) {
@@ -569,9 +596,10 @@
         // TODO(42240282): Store external textures in
         // BindGroupLayoutBase::BindingDataPointers::bindings so checking external textures can
         // be moved in the switch below.
+        UnpackedPtr<BindGroupEntry> unpacked;
+        DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(&entry));
+
         if (layout->GetExternalTextureBindingExpansionMap().contains(binding)) {
-            UnpackedPtr<BindGroupEntry> unpacked;
-            DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(&entry));
             if (auto* externalTextureBindingEntry = unpacked.Get<ExternalTextureBindingEntry>()) {
                 DAWN_TRY(ValidateExternalTextureBinding(
                     device, entry, externalTextureBindingEntry,
@@ -590,7 +618,12 @@
                 "entry.",
                 i);
         }
-        DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr.");
+
+        const TexelBufferBindingEntry* texelBufferEntry = unpacked.Get<TexelBufferBindingEntry>();
+        DAWN_INVALID_IF(
+            texelBufferEntry != nullptr &&
+                !std::holds_alternative<TexelBufferBindingInfo>(bindingInfo.bindingLayout),
+            "nextInChain must be nullptr.");
 
         // Perform binding-type specific validation.
         DAWN_TRY(MatchVariant(
@@ -622,6 +655,20 @@
                                  i, layout);
                 return {};
             },
+            [&](const TexelBufferBindingInfo& layout) -> MaybeError {
+                if (texelBufferEntry) {
+                    DAWN_TRY_CONTEXT(
+                        ValidateTexelBufferBinding(device, entry, texelBufferEntry, layout, mode),
+                        "validating entries[%u] as a Texel Buffer."
+                        "\nExpected entry layout: %s",
+                        i, layout);
+                    return {};
+                }
+                return DAWN_VALIDATION_ERROR(
+                    "entries[%u] not a TexelBuffer when the layout contains an TexelBuffer "
+                    "entry.",
+                    i);
+            },
             [&](const SamplerBindingInfo& layout) -> MaybeError {
                 DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, layout),
                                  "validating entries[%u] as a Sampler."
@@ -774,6 +821,12 @@
             continue;
         }
 
+        if (auto* texelBufferBindingEntry = entry.Get<TexelBufferBindingEntry>()) {
+            DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
+            mBindingData.bindings[bindingIndex] = texelBufferBindingEntry->texelBufferView;
+            continue;
+        }
+
         // Here we unpack external texture bindings into multiple additional bindings for the
         // external texture's contents. New binding locations previously determined in the bind
         // group layout are created in this bind group and filled with the external texture's
@@ -957,6 +1010,15 @@
             mBindingData.bufferData[bindingIndex].size};
 }
 
+TexelBufferViewBase* BindGroupBase::GetBindingAsTexelBufferView(BindingIndex bindingIndex) {
+    DAWN_ASSERT(!IsError());
+    const BindGroupLayoutInternalBase* layout = GetLayout();
+    DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
+    DAWN_ASSERT(std::holds_alternative<TexelBufferBindingInfo>(
+        layout->GetBindingInfo(bindingIndex).bindingLayout));
+    return static_cast<TexelBufferViewBase*>(mBindingData.bindings[bindingIndex].Get());
+}
+
 const std::vector<Ref<ExternalTextureBase>>& BindGroupBase::GetBoundExternalTextures() const {
     DAWN_ASSERT(!IsError());
     return mBoundExternalTextures;
diff --git a/src/dawn/native/BindGroup.h b/src/dawn/native/BindGroup.h
index e98e948..006e143 100644
--- a/src/dawn/native/BindGroup.h
+++ b/src/dawn/native/BindGroup.h
@@ -80,6 +80,7 @@
     SamplerBase* GetBindingAsSampler(BindingIndex bindingIndex) const;
     TextureViewBase* GetBindingAsTextureView(BindingIndex bindingIndex);
     BufferBinding GetBindingAsBufferBinding(BindingIndex bindingIndex);
+    TexelBufferViewBase* GetBindingAsTexelBufferView(BindingIndex bindingIndex);
     const ityp::span<uint32_t, uint64_t>& GetUnverifiedBufferSizes() const;
     const std::vector<Ref<ExternalTextureBase>>& GetBoundExternalTextures() const;
 
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index 5d490ab..6a79e85 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -512,22 +512,7 @@
     } else if (binding->storageTexture.access != wgpu::StorageTextureAccess::BindingNotUsed) {
         bindingInfo.bindingLayout = StorageTextureBindingInfo::From(binding->storageTexture);
     } else if (auto* texelBufferLayout = binding.Get<TexelBufferBindingLayout>()) {
-        // TODO(382544164): Prototype texel buffer feature.
-        // Placeholder implementation for `TexelBufferBindingLayout` from `TexelBufferBindingInfo`.
-        BufferBindingInfo bufferInfo{};
-        switch (texelBufferLayout->access) {
-            case wgpu::TexelBufferAccess::ReadOnly:
-                bufferInfo.type = wgpu::BufferBindingType::ReadOnlyStorage;
-                break;
-            case wgpu::TexelBufferAccess::ReadWrite:
-                bufferInfo.type = wgpu::BufferBindingType::Storage;
-                break;
-            default:
-                DAWN_UNREACHABLE();
-        }
-        bufferInfo.minBindingSize = 0;
-        bufferInfo.hasDynamicOffset = false;
-        bindingInfo.bindingLayout = bufferInfo;
+        bindingInfo.bindingLayout = TexelBufferBindingInfo::From(*texelBufferLayout);
     } else if (auto* staticSamplerBindingLayout = binding.Get<StaticSamplerBindingLayout>()) {
         bindingInfo.bindingLayout = StaticSamplerBindingInfo::From(*staticSamplerBindingLayout);
     } else {
@@ -706,6 +691,7 @@
                     mNeedsCrossBindingValidation = true;
                 }
             },
+            [&](const TexelBufferBindingInfo&) { counts[Order_TexelBuffer]++; },
             [&](const InputAttachmentBindingInfo&) { counts[Order_InputAttachment]++; });
     }
 
@@ -753,6 +739,7 @@
             [&](const StorageTextureBindingInfo&) { return Order_StorageTexture; },
             [&](const SamplerBindingInfo&) { return Order_RegularSampler; },
             [&](const StaticSamplerBindingInfo&) { return Order_StaticSampler; },
+            [&](const TexelBufferBindingInfo&) { return Order_TexelBuffer; },
             [&](const InputAttachmentBindingInfo&) { return Order_InputAttachment; });
     };
 
@@ -876,6 +863,9 @@
                 recorder.Record(BindingInfoType::StorageTexture, layout.access, layout.format,
                                 layout.viewDimension);
             },
+            [&](const TexelBufferBindingInfo& layout) {
+                recorder.Record(BindingInfoType::TexelBuffer, layout.format, layout.access);
+            },
             [&](const StaticSamplerBindingInfo& layout) {
                 recorder.Record(BindingInfoType::StaticSampler, layout.sampler->GetContentHash());
             },
@@ -961,6 +951,10 @@
                  GetBindingTypeEnd(Order_StorageTexture));
 }
 
+BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetTexelBufferIndices() const {
+    return Range(GetBindingTypeStart(Order_TexelBuffer), GetBindingTypeEnd(Order_TexelBuffer));
+}
+
 BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetSampledTextureIndices() const {
     return Range(GetBindingTypeStart(Order_SampledTexture),
                  GetBindingTypeEnd(Order_SampledTexture));
diff --git a/src/dawn/native/BindGroupLayoutInternal.h b/src/dawn/native/BindGroupLayoutInternal.h
index a3b6cf3..496b6df 100644
--- a/src/dawn/native/BindGroupLayoutInternal.h
+++ b/src/dawn/native/BindGroupLayoutInternal.h
@@ -101,6 +101,7 @@
     BeginEndRange<BindingIndex> GetDynamicBufferIndices() const;
     BeginEndRange<BindingIndex> GetBufferIndices() const;
     BeginEndRange<BindingIndex> GetStorageTextureIndices() const;
+    BeginEndRange<BindingIndex> GetTexelBufferIndices() const;
     BeginEndRange<BindingIndex> GetSampledTextureIndices() const;
     BeginEndRange<BindingIndex> GetTextureIndices() const;
     BeginEndRange<BindingIndex> GetSamplerIndices() const;
@@ -196,6 +197,8 @@
         // Samplers
         Order_StaticSampler,
         Order_RegularSampler,
+        // Texel Buffers
+        Order_TexelBuffer,
         Order_Count,
     };
     static bool SortBindingsCompare(const BindingInfo& a, const BindingInfo& b);
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index 97ff4ee..4a2ccde 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -46,6 +46,9 @@
         [](const StorageTextureBindingInfo&) -> BindingInfoType {
             return BindingInfoType::StorageTexture;
         },
+        [](const TexelBufferBindingInfo&) -> BindingInfoType {
+            return BindingInfoType::TexelBuffer;
+        },
         [](const StaticSamplerBindingInfo&) -> BindingInfoType {
             return BindingInfoType::StaticSampler;
         },
@@ -323,6 +326,17 @@
     }};
 }
 
+// TexelBufferBindingInfo
+
+// static
+TexelBufferBindingInfo TexelBufferBindingInfo::From(const TexelBufferBindingLayout& layout) {
+    TexelBufferBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
+    return {{
+        .format = defaultedLayout.format,
+        .access = defaultedLayout.access,
+    }};
+}
+
 // StorageTextureBindingInfo
 
 // static
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h
index 4b51171..451f343 100644
--- a/src/dawn/native/BindingInfo.h
+++ b/src/dawn/native/BindingInfo.h
@@ -63,6 +63,7 @@
     Sampler,
     Texture,
     StorageTexture,
+    TexelBuffer,
     ExternalTexture,
     StaticSampler,
     // Internal to vulkan only.
@@ -102,6 +103,15 @@
 };
 #undef STORAGE_TEXTURE_BINDING_INFO_MEMBER
 
+// A mirror of wgpu::TexelBufferBindingLayout for use inside dawn::native.
+#define TEXEL_BUFFER_BINDING_INFO_MEMBER(X) \
+    X(wgpu::TextureFormat, format)          \
+    X(wgpu::TexelBufferAccess, access)
+DAWN_SERIALIZABLE(struct, TexelBufferBindingInfo, TEXEL_BUFFER_BINDING_INFO_MEMBER) {
+    static TexelBufferBindingInfo From(const TexelBufferBindingLayout& layout);
+};
+#undef TEXEL_BUFFER_BINDING_INFO_MEMBER
+
 // A mirror of wgpu::SamplerBindingLayout for use inside dawn::native.
 #define SAMPLER_BINDING_INFO_MEMBER(X)                                               \
     /* For shader reflection NonFiltering is never used and Filtering is used for */ \
@@ -149,6 +159,7 @@
     std::variant<BufferBindingInfo,
                  SamplerBindingInfo,
                  TextureBindingInfo,
+                 TexelBufferBindingInfo,
                  StorageTextureBindingInfo,
                  StaticSamplerBindingInfo,
                  InputAttachmentBindingInfo>
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index 89f07f8..cc99ace 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -36,6 +36,7 @@
 #include "dawn/native/ExternalTexture.h"
 #include "dawn/native/Format.h"
 #include "dawn/native/QuerySet.h"
+#include "dawn/native/TexelBufferView.h"
 #include "dawn/native/Texture.h"
 
 namespace dawn::native {
@@ -198,6 +199,27 @@
         TextureViewUsedAs(view, usage, bindingInfo.visibility);
     }
 
+    for (BindingIndex i : layout->GetTexelBufferIndices()) {
+        const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(i);
+        const TexelBufferBindingInfo& texelInfo =
+            std::get<TexelBufferBindingInfo>(bindingInfo.bindingLayout);
+
+        wgpu::BufferUsage usage = wgpu::BufferUsage::None;
+        switch (texelInfo.access) {
+            case wgpu::TexelBufferAccess::ReadOnly:
+                usage = kReadOnlyTexelBuffer;
+                break;
+            case wgpu::TexelBufferAccess::ReadWrite:
+                usage = wgpu::BufferUsage::Storage;
+                break;
+            case wgpu::TexelBufferAccess::Undefined:
+                DAWN_UNREACHABLE();
+        }
+
+        BufferBase* buffer = group->GetBindingAsTexelBufferView(i)->GetBuffer();
+        BufferUsedAs(buffer, usage, bindingInfo.visibility);
+    }
+
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
         mExternalTextureUsages.insert(externalTexture.Get());
     }
@@ -263,6 +285,10 @@
         mUsage.referencedTextures.insert(group->GetBindingAsTextureView(i)->GetTexture());
     }
 
+    for (BindingIndex i : layout->GetTexelBufferIndices()) {
+        mUsage.referencedBuffers.insert(group->GetBindingAsTexelBufferView(i)->GetBuffer());
+    }
+
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
         mUsage.referencedExternalTextures.insert(externalTexture.Get());
     }
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index 23a4ab8..ca7a55e 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -29,6 +29,7 @@
 
 #include <algorithm>
 #include <map>
+#include <memory>
 #include <utility>
 
 #include "absl/container/inlined_vector.h"
@@ -274,9 +275,12 @@
     };
 
     // Does the trivial conversions from a ShaderBindingInfo to a BindGroupLayoutEntry
+    std::vector<std::unique_ptr<wgpu::TexelBufferBindingLayout>> texelBufferLayouts;
+
     auto ConvertMetadataToEntry =
-        [](const ShaderBindingInfo& shaderBinding,
-           const ExternalTextureBindingLayout* externalTextureBindingEntry) -> EntryData {
+        [&texelBufferLayouts](
+            BindGroupIndex /*group*/, const ShaderBindingInfo& shaderBinding,
+            const ExternalTextureBindingLayout* externalTextureBindingEntry) -> EntryData {
         EntryData entry = {};
         entry.bindingArraySize = uint32_t(shaderBinding.arraySize);
 
@@ -303,6 +307,13 @@
                 entry.storageTexture.format = bindingInfo.format;
                 entry.storageTexture.viewDimension = bindingInfo.viewDimension;
             },
+            [&](const TexelBufferBindingInfo& bindingInfo) {
+                auto layout = std::make_unique<wgpu::TexelBufferBindingLayout>();
+                layout->format = bindingInfo.format;
+                layout->access = bindingInfo.access;
+                texelBufferLayouts.push_back(std::move(layout));
+                entry.nextInChain = texelBufferLayouts.back().get();
+            },
             [&](const ExternalTextureBindingInfo&) {
                 entry.nextInChain = externalTextureBindingEntry;
             },
@@ -373,7 +384,7 @@
             for (const auto& [bindingNumber, shaderBinding] : groupBindings) {
                 // Create the BindGroupLayoutEntry
                 EntryData entry =
-                    ConvertMetadataToEntry(shaderBinding, &externalTextureBindingLayout);
+                    ConvertMetadataToEntry(group, shaderBinding, &externalTextureBindingLayout);
                 entry.binding = uint32_t(bindingNumber);
                 entry.visibility = StageBit(stage.shaderStage);
 
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index c54ee5d..c81e0fd 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -89,6 +89,9 @@
         case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageTexture:
         case tint::inspector::ResourceBinding::ResourceType::kReadWriteStorageTexture:
             return BindingInfoType::StorageTexture;
+        case tint::inspector::ResourceBinding::ResourceType::kReadOnlyTexelBuffer:
+        case tint::inspector::ResourceBinding::ResourceType::kReadWriteTexelBuffer:
+            return BindingInfoType::TexelBuffer;
         case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
             return BindingInfoType::ExternalTexture;
         case tint::inspector::ResourceBinding::ResourceType::kInputAttachment:
@@ -291,6 +294,19 @@
     DAWN_UNREACHABLE();
 }
 
+ResultOrError<wgpu::TexelBufferAccess> TintResourceTypeToTexelBufferAccess(
+    tint::inspector::ResourceBinding::ResourceType resource_type) {
+    switch (resource_type) {
+        case tint::inspector::ResourceBinding::ResourceType::kReadOnlyTexelBuffer:
+            return wgpu::TexelBufferAccess::ReadOnly;
+        case tint::inspector::ResourceBinding::ResourceType::kReadWriteTexelBuffer:
+            return wgpu::TexelBufferAccess::ReadWrite;
+        default:
+            return DAWN_VALIDATION_ERROR("Attempted to convert non-texel buffer resource type");
+    }
+    DAWN_UNREACHABLE();
+}
+
 ResultOrError<InterStageComponentType> TintComponentTypeToInterStageComponentType(
     tint::inspector::ComponentType type) {
     switch (type) {
@@ -576,6 +592,7 @@
         [](const SamplerBindingInfo&) { return BindingInfoType::Sampler; },
         [](const TextureBindingInfo&) { return BindingInfoType::Texture; },
         [](const StorageTextureBindingInfo&) { return BindingInfoType::StorageTexture; },
+        [](const TexelBufferBindingInfo&) { return BindingInfoType::TexelBuffer; },
         [](const ExternalTextureBindingInfo&) { return BindingInfoType::ExternalTexture; },
         [](const InputAttachmentBindingInfo&) { return BindingInfoType::InputAttachment; });
 }
@@ -697,6 +714,23 @@
                             bindingLayout.viewDimension, bindingInfo.viewDimension);
             return {};
         },
+        [&](const TexelBufferBindingInfo& bindingInfo) -> MaybeError {
+            const TexelBufferBindingInfo& bindingLayout =
+                std::get<TexelBufferBindingInfo>(layoutInfo.bindingLayout);
+            DAWN_ASSERT(bindingLayout.format != wgpu::TextureFormat::Undefined);
+            DAWN_ASSERT(bindingInfo.format != wgpu::TextureFormat::Undefined);
+
+            DAWN_INVALID_IF(bindingLayout.access != bindingInfo.access,
+                            "The layout's binding access (%s) doesn't match the shader's binding "
+                            "access (%s).",
+                            bindingLayout.access, bindingInfo.access);
+
+            DAWN_INVALID_IF(bindingLayout.format != bindingInfo.format,
+                            "The layout's binding format (%s) doesn't match the shader's binding "
+                            "format (%s).",
+                            bindingLayout.format, bindingInfo.format);
+            return {};
+        },
         [&](const BufferBindingInfo& bindingInfo) -> MaybeError {
             const BufferBindingInfo& bindingLayout =
                 std::get<BufferBindingInfo>(layoutInfo.bindingLayout);
@@ -1203,6 +1237,16 @@
                 break;
             }
 
+            case BindingInfoType::TexelBuffer: {
+                TexelBufferBindingInfo bindingInfo = {};
+                DAWN_TRY_ASSIGN(bindingInfo.access,
+                                TintResourceTypeToTexelBufferAccess(resource.resource_type));
+                bindingInfo.format = TintImageFormatToTextureFormat(resource.image_format);
+
+                info.bindingInfo = bindingInfo;
+                break;
+            }
+
             case BindingInfoType::ExternalTexture: {
                 info.bindingInfo.emplace<ExternalTextureBindingInfo>();
                 break;
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index fa70634..2fa5c26 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -188,6 +188,7 @@
                                               SamplerBindingInfo,
                                               TextureBindingInfo,
                                               StorageTextureBindingInfo,
+                                              TexelBufferBindingInfo,
                                               ExternalTextureBindingInfo,
                                               InputAttachmentBindingInfo>;
 #define SHADER_BINDING_INFO_MEMBER(X)              \
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
index 1107aed..ed1b534 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
@@ -616,6 +616,12 @@
                 }
                 return {};
             },
+            [&](const TexelBufferBindingInfo&) -> MaybeError {
+                // D3D11 does not support texel buffers.
+                // TODO(crbug/382544164): Prototype texel buffer feature
+                DAWN_UNREACHABLE();
+                return {};
+            },
             [](const InputAttachmentBindingInfo&) -> MaybeError {
                 DAWN_UNREACHABLE();
                 return {};
@@ -689,6 +695,11 @@
                         DAWN_UNREACHABLE();
                 }
             },
+            [&](const TexelBufferBindingInfo&) {
+                // D3D11 does not support texel buffers.
+                // TODO(crbug/382544164): Prototype texel buffer feature
+                DAWN_UNREACHABLE();
+            },
             [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
     }
 }
@@ -815,6 +826,12 @@
                     DAWN_UNREACHABLE();
                     return {};
                 },
+                [](const TexelBufferBindingInfo&) -> MaybeError {
+                    // D3D11 does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                    return {};
+                },
                 [](const InputAttachmentBindingInfo&) -> MaybeError {
                     DAWN_UNREACHABLE();
                     return {};
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
index 744766c..d283841 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
@@ -133,6 +133,12 @@
                     }
                     return kInvalidSlots;
                 },
+                [&](const TexelBufferBindingInfo&) -> PerStage<uint32_t> {
+                    // D3D11 does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                    return kInvalidSlots;
+                },
                 [&](const InputAttachmentBindingInfo&) -> PerStage<uint32_t> {
                     DAWN_UNREACHABLE();
                     return kInvalidSlots;
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.cpp b/src/dawn/native/d3d12/BindGroupD3D12.cpp
index 22dbf99..104d096 100644
--- a/src/dawn/native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupD3D12.cpp
@@ -200,6 +200,11 @@
                         DAWN_UNREACHABLE();
                 }
             },
+            [&](const TexelBufferBindingInfo&) {
+                // D3D12 does not support texel buffers.
+                // TODO(crbug/382544164): Prototype texel buffer feature
+                DAWN_UNREACHABLE();
+            },
             [](const StaticSamplerBindingInfo&) {
                 // Static samplers are already initialized in the pipeline layout.
             },
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
index 9441520..e2a6615 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
@@ -79,6 +79,12 @@
                     DAWN_UNREACHABLE();
             }
         },
+        [](const TexelBufferBindingInfo&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
+            // D3D12 does not support texel buffers.
+            // TODO(crbug/382544164): Prototype texel buffer feature
+            DAWN_UNREACHABLE();
+            return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+        },
         [](const InputAttachmentBindingInfo&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
             DAWN_UNREACHABLE();
             return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
@@ -193,6 +199,12 @@
             [](const StorageTextureBindingInfo&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
                 return D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
             },
+            [](const TexelBufferBindingInfo&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                // D3D12 does not support texel buffers.
+                // TODO(crbug/382544164): Prototype texel buffer feature
+                DAWN_UNREACHABLE();
+                return D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
+            },
             [](const InputAttachmentBindingInfo&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
                 DAWN_UNREACHABLE();
                 return D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
diff --git a/src/dawn/native/metal/BindGroupLayoutMTL.mm b/src/dawn/native/metal/BindGroupLayoutMTL.mm
index 4bf029f..b5eb201 100644
--- a/src/dawn/native/metal/BindGroupLayoutMTL.mm
+++ b/src/dawn/native/metal/BindGroupLayoutMTL.mm
@@ -76,6 +76,7 @@
             },
             [&](const TextureBindingInfo&) { desc.dataType = MTLDataTypeTexture; },
             [&](const StorageTextureBindingInfo&) { DAWN_CHECK(false); },
+            [&](const TexelBufferBindingInfo&) { DAWN_CHECK(false); },
             [](const InputAttachmentBindingInfo&) { DAWN_CHECK(false); });
 
         descriptors.push_back(desc);
diff --git a/src/dawn/native/metal/BindGroupMTL.mm b/src/dawn/native/metal/BindGroupMTL.mm
index 894967a..e79f2a3 100644
--- a/src/dawn/native/metal/BindGroupMTL.mm
+++ b/src/dawn/native/metal/BindGroupMTL.mm
@@ -103,6 +103,11 @@
             },
             [&](const TextureBindingInfo&) { HandleTextureBinding(); },
             [&](const StorageTextureBindingInfo&) { HandleTextureBinding(); },
+            [&](const TexelBufferBindingInfo&) {
+                // Metal does not support texel buffers.
+                // TODO(crbug/382544164): Prototype texel buffer feature
+                DAWN_CHECK(false);
+            },
             [](const InputAttachmentBindingInfo&) { DAWN_CHECK(false); });
     }
 
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index 8e935c8..ce91b34 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -824,6 +824,11 @@
                 },
                 [&](const TextureBindingInfo&) { HandleTextureBinding(); },
                 [&](const StorageTextureBindingInfo&) { HandleTextureBinding(); },
+                [&](const TexelBufferBindingInfo&) {
+                    // Metal does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                },
                 [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
         }
     }
@@ -853,6 +858,11 @@
                 },
                 [&](const TextureBindingInfo&) {},  //
                 [&](const StorageTextureBindingInfo&) {},
+                [&](const TexelBufferBindingInfo&) {
+                    // Metal does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_CHECK(false);
+                },
                 [](const InputAttachmentBindingInfo&) { DAWN_CHECK(false); });
         }
 
diff --git a/src/dawn/native/metal/PipelineLayoutMTL.mm b/src/dawn/native/metal/PipelineLayoutMTL.mm
index 323819a..ecb671f 100644
--- a/src/dawn/native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn/native/metal/PipelineLayoutMTL.mm
@@ -78,6 +78,11 @@
                         mIndexInfo[stage][group][bindingIndex] = textureIndex;
                         textureIndex++;
                     },
+                    [&](const TexelBufferBindingInfo&) {
+                        // Metal does not support texel buffers.
+                        // TODO(crbug/382544164): Prototype texel buffer feature
+                        DAWN_UNREACHABLE();
+                    },
                     [&](const StaticSamplerBindingInfo&) {
                         // Static samplers are handled in the frontend.
                         // TODO(crbug.com/dawn/2482): Implement static samplers in the
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index dfa18ab..479e81c 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -192,6 +192,11 @@
                 [&](const StorageTextureBindingInfo& bindingInfo) {
                     bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
                 },
+                [&](const TexelBufferBindingInfo& bindingInfo) {
+                    // Metal does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
                     const auto& etBindingMap = bgl->GetExternalTextureBindingExpansionMap();
                     const auto& expansion = etBindingMap.find(binding);
@@ -261,7 +266,7 @@
                 },
                 [&](const SamplerBindingInfo& bindingInfo) {},
                 [&](const StaticSamplerBindingInfo& bindingInfo) {},
-                [&](const TextureBindingInfo& bindingInfo) {},
+                [&](const TextureBindingInfo& bindingInfo) {}, [](const TexelBufferBindingInfo&) {},
                 [&](const StorageTextureBindingInfo& bindingInfo) {},
                 [](const InputAttachmentBindingInfo&) { DAWN_CHECK(false); });
         }
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 03b29c0..5ac27fa 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -487,6 +487,12 @@
                                              texture->GetGLFormat().internalFormat));
                     return {};
                 },
+                [&](const TexelBufferBindingInfo&) -> MaybeError {
+                    // OpenGL does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                    return {};
+                },
                 [](const InputAttachmentBindingInfo&) -> MaybeError { DAWN_UNREACHABLE(); }));
         }
 
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.cpp b/src/dawn/native/opengl/PipelineLayoutGL.cpp
index 02718c3..274f988 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn/native/opengl/PipelineLayoutGL.cpp
@@ -84,6 +84,11 @@
                     mIndexInfo[group][bindingIndex] = storageTextureIndex;
                     storageTextureIndex++;
                 },
+                [&](const TexelBufferBindingInfo&) {
+                    // OpenGL does not support texel buffers.
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                },
                 [](const InputAttachmentBindingInfo&) { DAWN_UNREACHABLE(); });
         }
     }
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index 41ec8ed..8693f67 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -275,7 +275,7 @@
                 },
                 [](const StaticSamplerBindingInfo&) {}, [](const SamplerBindingInfo&) {},
                 [](const TextureBindingInfo&) {}, [](const StorageTextureBindingInfo&) {},
-                [](const InputAttachmentBindingInfo&) {});
+                [](const TexelBufferBindingInfo&) {}, [](const InputAttachmentBindingInfo&) {});
         }
     }
 
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
index d6e380d..7326af4 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
@@ -222,6 +222,12 @@
         },
         [](const TextureBindingInfo&) { return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; },
         [](const StorageTextureBindingInfo&) { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; },
+        [](const TexelBufferBindingInfo&) {
+            // TODO(crbug/382544164): Prototype texel buffer feature
+            DAWN_UNREACHABLE();
+            return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
+        },
+
         [](const InputAttachmentBindingInfo&) { return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; });
 }
 
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index 8a03626..844aba7 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -196,6 +196,10 @@
                 [&](const StorageTextureBindingInfo& bindingInfo) {
                     bindings.storage_texture.emplace(srcBindingPoint, dstBindingPoint);
                 },
+                [&](const TexelBufferBindingInfo& bindingInfo) {
+                    // TODO(crbug/382544164): Prototype texel buffer feature
+                    DAWN_UNREACHABLE();
+                },
                 [&](const ExternalTextureBindingInfo& bindingInfo) {
                     const auto& bindingMap = bgl->GetExternalTextureBindingExpansionMap();
                     const auto& expansion = bindingMap.find(binding);
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 5b886af..e1c21c9 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -139,6 +139,9 @@
         [&](const StorageTextureBindingInfo& layout) {
             s->Append(absl::StrFormat("%s: %s ", BindingInfoType::StorageTexture, layout));
         },
+        [&](const TexelBufferBindingInfo& layout) {
+            s->Append(absl::StrFormat("%s: %s ", BindingInfoType::TexelBuffer, layout));
+        },
         [&](const InputAttachmentBindingInfo& layout) {
             s->Append(absl::StrFormat("%s: %s ", BindingInfoType::InputAttachment, layout));
         });
@@ -199,6 +202,22 @@
 }
 
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const TexelBufferBindingInfo& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    s->Append(absl::StrFormat("{format: %s, access: %s}", value.format, value.access));
+    return {true};
+}
+
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const TexelBufferBindingLayout& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    auto info = TexelBufferBindingInfo::From(value);
+    return AbslFormatConvert(info, spec, s);
+}
+
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const SamplerBindingInfo& value,
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s) {
@@ -544,6 +563,9 @@
         case BindingInfoType::ExternalTexture:
             s->Append("externalTexture");
             break;
+        case BindingInfoType::TexelBuffer:
+            s->Append("texelBuffer");
+            break;
         case BindingInfoType::StaticSampler:
             s->Append("staticSampler");
             break;
diff --git a/src/dawn/native/webgpu_absl_format.h b/src/dawn/native/webgpu_absl_format.h
index b8e19e1..b52b699 100644
--- a/src/dawn/native/webgpu_absl_format.h
+++ b/src/dawn/native/webgpu_absl_format.h
@@ -120,6 +120,18 @@
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s);
 
+struct TexelBufferBindingInfo;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const TexelBufferBindingInfo& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
+struct TexelBufferBindingLayout;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const TexelBufferBindingLayout& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
 struct SamplerBindingInfo;
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const SamplerBindingInfo& value,
diff --git a/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
index 120f00b..cc38b5a 100644
--- a/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -549,6 +549,35 @@
                 BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
 }
 
+// Tests that the texel buffer binding type matches with a texel_buffer declared in the shader.
+TEST_F(GetBindGroupLayoutTests, TexelBufferBindingType) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    wgpu::TexelBufferBindingLayout layout = {};
+    layout.access = wgpu::TexelBufferAccess::ReadOnly;
+    layout.format = wgpu::TextureFormat::RGBA8Uint;
+
+    wgpu::BindGroupLayoutEntry entry = {};
+    entry.binding = 0;
+    entry.visibility = wgpu::ShaderStage::Fragment;
+    entry.nextInChain = &layout;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &entry;
+
+    wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"(
+            requires texel_buffers;
+            @group(0) @binding(0) var myTexelBuffer: texel_buffer<rgba8uint, read>;
+
+            @fragment fn main() {
+                _ = textureDimensions(myTexelBuffer);
+            })");
+
+    EXPECT_THAT(device.CreateBindGroupLayout(&desc),
+                BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)));
+}
+
 // Test that texture view dimension matches the shader.
 TEST_F(GetBindGroupLayoutTests, TextureViewDimension) {
     DAWN_SKIP_TEST_IF(UsesWire());
diff --git a/src/dawn/tests/unittests/validation/TexelBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/TexelBufferValidationTests.cpp
index e1edd1f..94701df 100644
--- a/src/dawn/tests/unittests/validation/TexelBufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/TexelBufferValidationTests.cpp
@@ -142,6 +142,85 @@
     EXPECT_EQ(texelEntry->texelBufferView.Get(), view.Get());
 }
 
+// The format of a bound texel buffer view must match the layout.
+TEST_F(TexelBufferValidationTest, ViewFormatMustMatchLayout) {
+    wgpu::BufferDescriptor desc;
+    desc.size = 256;
+    desc.usage = wgpu::BufferUsage::TexelBuffer;
+    wgpu::Buffer buffer = device.CreateBuffer(&desc);
+
+    wgpu::TexelBufferViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::RGBA8Uint;
+    viewDesc.offset = 0;
+    viewDesc.size = 256;
+    wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc);
+
+    wgpu::TexelBufferBindingLayout layout = {};
+    layout.access = wgpu::TexelBufferAccess::ReadOnly;
+    layout.format = wgpu::TextureFormat::R32Uint;
+
+    wgpu::BindGroupLayout bgl =
+        utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layout}});
+
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}}));
+}
+
+// Read-write texel buffer bindings require the buffer to also have STORAGE usage.
+TEST_F(TexelBufferValidationTest, ReadWriteBindingRequiresStorageUsage) {
+    wgpu::BufferDescriptor desc;
+    desc.size = 256;
+    desc.usage = wgpu::BufferUsage::TexelBuffer;
+    wgpu::Buffer buffer = device.CreateBuffer(&desc);
+
+    wgpu::TexelBufferViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::R32Uint;
+    viewDesc.offset = 0;
+    viewDesc.size = 4;
+    wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc);
+
+    wgpu::TexelBufferBindingLayout layout = {};
+    layout.access = wgpu::TexelBufferAccess::ReadWrite;
+    layout.format = wgpu::TextureFormat::R32Uint;
+
+    wgpu::BindGroupLayout bgl =
+        utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layout}});
+
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}}));
+}
+
+// Binding a TexelBuffer to a texture binding slot fails.
+TEST_F(TexelBufferValidationTest, TexelBufferCannotBindToTextureSlot) {
+    wgpu::BindGroupLayoutEntry textureEntry = {};
+    textureEntry.binding = 0;
+    textureEntry.visibility = wgpu::ShaderStage::Compute;
+    textureEntry.texture.sampleType = wgpu::TextureSampleType::Float;
+    textureEntry.texture.viewDimension = wgpu::TextureViewDimension::e2D;
+
+    wgpu::BindGroupLayoutDescriptor bglDesc = {};
+    bglDesc.entryCount = 1;
+    bglDesc.entries = &textureEntry;
+    wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&bglDesc);
+
+    wgpu::Buffer buffer = CreateTexelBuffer(4, wgpu::BufferUsage::TexelBuffer);
+    wgpu::TexelBufferViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::R32Uint;
+    wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc);
+
+    wgpu::TexelBufferBindingEntry texelEntry = {};
+    texelEntry.texelBufferView = view;
+
+    wgpu::BindGroupEntry bgEntry = {};
+    bgEntry.binding = 0;
+    bgEntry.nextInChain = &texelEntry;
+
+    wgpu::BindGroupDescriptor bgDesc = {};
+    bgDesc.layout = bgl;
+    bgDesc.entryCount = 1;
+    bgDesc.entries = &bgEntry;
+
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&bgDesc));
+}
+
 class TexelBufferValidationWithExtendedMapTest : public TexelBufferValidationTest {
   protected:
     void SetUp() override {