[dawn][native] Add BGL extension and validation for bindless.

Bug: 435251399
Change-Id: I822a830e4b5d4ee76bb475e3be2d47e557703ae7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/256215
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 0d0d96b..c9798dc 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -512,12 +512,39 @@
             {"name": "storage texture", "type": "storage texture binding layout", "default": "zero"}
         ]
     },
+
+    "dynamic binding kind": {
+        "category": "enum",
+        "tags": ["dawn"],
+        "values": [
+            {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false},
+            {"value": 1, "name": "sampled texture"}
+        ]
+    },
+    "dynamic binding array layout": {
+        "category": "structure",
+        "tags": ["dawn"],
+        "extensible": "in",
+        "members": [
+            {"name": "start", "type": "uint32_t", "default": 0},
+            {"name": "kind", "type": "dynamic binding kind"}
+        ]
+    },
+    "bind group layout dynamic binding array": {
+        "category": "structure",
+        "tags": ["dawn"],
+        "chained": "in",
+        "chain roots": ["bind group layout descriptor"],
+        "members": [
+            {"name": "dynamic array", "type": "dynamic binding array layout"}
+        ]
+    },
     "bind group layout descriptor": {
         "category": "structure",
         "extensible": "in",
         "members": [
             {"name": "label", "type": "string view", "optional": true},
-            {"name": "entry count", "type": "size_t"},
+            {"name": "entry count", "type": "size_t", "default": 0},
             {"name": "entries", "type": "bind group layout entry", "annotation": "const*", "length": "entry count"}
         ]
     },
@@ -3902,7 +3929,8 @@
             {"value": 70, "name": "dawn fake device initialize error for testing", "tags": ["dawn"]},
             {"value": 71, "name": "texture component swizzle descriptor", "tags": ["dawn"]},
             {"value": 72, "name": "shared texture memory D3D11 begin state", "tags": ["dawn", "native"]},
-            {"value": 73, "name": "dawn consume adapter descriptor", "tags": ["dawn"]}
+            {"value": 73, "name": "dawn consume adapter descriptor", "tags": ["dawn"]},
+            {"value": 74, "name": "bind group layout dynamic binding array", "tags": ["dawn"]}
         ]
     },
     "texture": {
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index 1c454a1..92c135f 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -280,7 +280,7 @@
 
 MaybeError ValidateStaticSamplersWithTextureBindings(
     DeviceBase* device,
-    const BindGroupLayoutDescriptor* descriptor,
+    const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor,
     const std::map<BindingNumber, uint32_t>& bindingNumberToIndexMap) {
     // Map of texture binding number to static sampler binding number.
     std::map<BindingNumber, BindingNumber> textureToStaticSamplerBindingMap;
@@ -322,9 +322,28 @@
 }  // anonymous namespace
 
 MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
-                                             const BindGroupLayoutDescriptor* descriptor,
+                                             const BindGroupLayoutDescriptor* descriptorChain,
                                              bool allowInternalBinding) {
-    DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr");
+    UnpackedPtr<BindGroupLayoutDescriptor> descriptor;
+    DAWN_TRY_ASSIGN(descriptor, ValidateAndUnpack(descriptorChain));
+
+    // Handle the dynamic binding array first to also extract information needed to validate the
+    // rest of the bindings.
+    std::optional<BindingNumber> startOfDynamicArray = {};
+    if (auto* dynamic = descriptor.Get<BindGroupLayoutDynamicBindingArray>()) {
+        DAWN_INVALID_IF(!device->HasFeature(Feature::ChromiumExperimentalBindless),
+                        "Dynamic binding array used without the %s feature enabled.",
+                        wgpu::FeatureName::ChromiumExperimentalBindless);
+        DAWN_INVALID_IF(dynamic->dynamicArray.nextInChain != nullptr,
+                        "DynamicBindingArrayLayout::nextInChain must be nullptr");
+
+        startOfDynamicArray = BindingNumber(dynamic->dynamicArray.start);
+        DAWN_TRY(ValidateDynamicBindingKind(dynamic->dynamicArray.kind));
+
+        DAWN_INVALID_IF(startOfDynamicArray >= kMaxBindingsPerBindGroupTyped,
+                        "dynamic array start (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
+                        startOfDynamicArray.value(), kMaxBindingsPerBindGroup);
+    }
 
     // Map of binding number to entry index.
     std::map<BindingNumber, uint32_t> bindingMap;
@@ -338,7 +357,7 @@
         DAWN_INVALID_IF(
             bindingNumber >= kMaxBindingsPerBindGroupTyped,
             "On entries[%u]: binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
-            i, uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
+            i, bindingNumber, kMaxBindingsPerBindGroup);
 
         BindingNumber arraySize{1};
         if (entry->bindingArraySize > 1) {
@@ -357,6 +376,12 @@
                             kMaxBindingsPerBindGroupTyped);
         }
 
+        DAWN_INVALID_IF(startOfDynamicArray.has_value() &&
+                            bindingNumber + arraySize > startOfDynamicArray.value(),
+                        "On entries[%u]: the range of binding used [%u, %u) conflicts with the "
+                        "dynamic binding array that starts at binding %u.",
+                        i, bindingNumber, bindingNumber + arraySize, startOfDynamicArray.value());
+
         // Check that the same binding is not set twice. bindingNumber + arraySize cannot overflow
         // as they are both smaller than kMaxBindingsPerBindGroupTyped.
         static_assert(kMaxBindingsPerBindGroup < std::numeric_limits<uint32_t>::max() / 2);
@@ -448,7 +473,8 @@
     ityp::vector<BindingIndex, BindingInfo> entries;
     ExternalTextureBindingExpansionMap externalTextureBindingExpansions;
 };
-ExpandedBindingInfo ConvertAndExpandBGLEntries(const BindGroupLayoutDescriptor* descriptor) {
+ExpandedBindingInfo ConvertAndExpandBGLEntries(
+    const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor) {
     ExpandedBindingInfo result;
 
     // When new bgl entries are created, we use binding numbers larger than kMaxBindingsPerBindGroup
@@ -550,9 +576,11 @@
 
 BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
     DeviceBase* device,
-    const BindGroupLayoutDescriptor* descriptor,
+    const BindGroupLayoutDescriptor* descriptorChain,
     ApiObjectBase::UntrackedByDeviceTag tag)
-    : ApiObjectBase(device, descriptor->label) {
+    : ApiObjectBase(device, descriptorChain->label) {
+    UnpackedPtr<BindGroupLayoutDescriptor> descriptor = Unpack(descriptorChain);
+
     ExpandedBindingInfo unpackedBindings = ConvertAndExpandBGLEntries(descriptor);
     mExternalTextureBindingExpansionMap =
         std::move(unpackedBindings.externalTextureBindingExpansions);
@@ -611,6 +639,13 @@
         UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]);
         IncrementBindingCounts(&mValidationBindingCounts, entry);
     }
+
+    // Handle the dynamic binding array if there is one.
+    if (auto* dynamic = descriptor.Get<BindGroupLayoutDynamicBindingArray>()) {
+        mHasDynamicArray = true;
+        mDynamicArrayStart = BindingNumber(dynamic->dynamicArray.start);
+        mDynamicArrayKind = dynamic->dynamicArray.kind;
+    }
 }
 
 BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(
@@ -658,6 +693,18 @@
     return it->second;
 }
 
+bool BindGroupLayoutInternalBase::HasDynamicArray() const {
+    return mHasDynamicArray;
+}
+
+BindingNumber BindGroupLayoutInternalBase::GetDynamicArrayStart() const {
+    return mDynamicArrayStart;
+}
+
+wgpu::DynamicBindingKind BindGroupLayoutInternalBase::GetDynamicArrayKind() const {
+    return mDynamicArrayKind;
+}
+
 void BindGroupLayoutInternalBase::ReduceMemoryUsage() {}
 
 size_t BindGroupLayoutInternalBase::ComputeContentHash() {
@@ -698,6 +745,8 @@
             });
     }
 
+    recorder.Record(mHasDynamicArray, mDynamicArrayStart, mDynamicArrayKind);
+
     return recorder.GetContentHash();
 }
 
@@ -712,12 +761,21 @@
             return false;
         }
     }
-    return a->mBindingMap == b->mBindingMap;
+    if (a->mBindingMap != b->mBindingMap) {
+        return false;
+    }
+
+    if (a->mHasDynamicArray != b->mHasDynamicArray ||
+        a->mDynamicArrayKind != b->mDynamicArrayKind ||
+        a->mDynamicArrayStart != b->mDynamicArrayStart) {
+        return false;
+    }
+    return true;
 }
 
 bool BindGroupLayoutInternalBase::IsEmpty() const {
     DAWN_ASSERT(!IsError());
-    return mBindingInfo.empty();
+    return mBindingInfo.empty() && !mHasDynamicArray;
 }
 
 BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const {
diff --git a/src/dawn/native/BindGroupLayoutInternal.h b/src/dawn/native/BindGroupLayoutInternal.h
index 91a0211..65c6208 100644
--- a/src/dawn/native/BindGroupLayoutInternal.h
+++ b/src/dawn/native/BindGroupLayoutInternal.h
@@ -80,11 +80,17 @@
     // A map from the BindingNumber to its packed BindingIndex.
     using BindingMap = std::map<BindingNumber, BindingIndex>;
 
+    // Getters for static bindings
     const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const;
     const BindingMap& GetBindingMap() const;
     bool HasBinding(BindingNumber bindingNumber) const;
     BindingIndex GetBindingIndex(BindingNumber bindingNumber) const;
 
+    // Getters for the dynamic binding array.
+    bool HasDynamicArray() const;
+    BindingNumber GetDynamicArrayStart() const;
+    wgpu::DynamicBindingKind GetDynamicArrayKind() const;
+
     // Signals it's an appropriate time to free unused memory. BindGroupLayout implementations often
     // have SlabAllocator<BindGroup> that need an external signal.
     virtual void ReduceMemoryUsage();
@@ -175,6 +181,11 @@
 
     BindingCounts mValidationBindingCounts = {};
     bool mNeedsCrossBindingValidation = false;
+
+    // Information about the dynamic binding array part of the BGL.
+    bool mHasDynamicArray = false;
+    BindingNumber mDynamicArrayStart{0};
+    wgpu::DynamicBindingKind mDynamicArrayKind = wgpu::DynamicBindingKind::Undefined;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
index 284e3e5..176bed8 100644
--- a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -4344,5 +4344,109 @@
     }
 }
 
+class BindGroupValidationTest_ChromiumExperimentalBindless : public BindGroupValidationTest {
+  protected:
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        return {wgpu::FeatureName::ChromiumExperimentalBindless};
+    }
+};
+
+// Control case where creating a dynamic binding array with the feature enabled is valid.
+TEST_F(BindGroupValidationTest_ChromiumExperimentalBindless, SuccessWithFeatureEnabled) {
+    wgpu::BindGroupLayoutDynamicBindingArray dynamic;
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
+
+    wgpu::BindGroupLayoutDescriptor desc;
+    desc.nextInChain = &dynamic;
+
+    // No error is produced.
+    device.CreateBindGroupLayout(&desc);
+}
+
+// Error case where creating a dynamic binding array with the feature disabled is an error.
+TEST_F(BindGroupValidationTest, ErrorWithFeatureDisabled) {
+    wgpu::BindGroupLayoutDynamicBindingArray dynamic;
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
+
+    wgpu::BindGroupLayoutDescriptor desc;
+    desc.nextInChain = &dynamic;
+
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that using DynamicArrayKind::Undefined is an error.
+TEST_F(BindGroupValidationTest_ChromiumExperimentalBindless, UndefinedArrayKind) {
+    wgpu::BindGroupLayoutDynamicBindingArray dynamic;
+
+    wgpu::BindGroupLayoutDescriptor desc;
+    desc.nextInChain = &dynamic;
+
+    // Control case: SampledTexture is a valid kind.
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
+    device.CreateBindGroupLayout(&desc);
+
+    // Error case: Undefined is invalid.
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::Undefined;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that the start of the binding array must be less than maxBindingsPerBindGroup
+TEST_F(BindGroupValidationTest_ChromiumExperimentalBindless, DynamicArrayStartLimit) {
+    wgpu::BindGroupLayoutDynamicBindingArray dynamic;
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
+
+    wgpu::BindGroupLayoutDescriptor desc;
+    desc.nextInChain = &dynamic;
+
+    // No error is produced if we are under the limit.
+    dynamic.dynamicArray.start = kMaxBindingsPerBindGroup - 1;
+    device.CreateBindGroupLayout(&desc);
+
+    // Error case if we are at the limit.
+    dynamic.dynamicArray.start = kMaxBindingsPerBindGroup;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+
+    // Error case if we are above the limit.
+    dynamic.dynamicArray.start = kMaxBindingsPerBindGroup + 1;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that conflicts of binding number are not allowed between dynamic and static bindings.
+TEST_F(BindGroupValidationTest_ChromiumExperimentalBindless, ConflictWithStaticBindings) {
+    wgpu::BindGroupLayoutEntry entry;
+    entry.binding = 0;
+    entry.bindingArraySize = 0;
+    entry.texture.sampleType = wgpu::TextureSampleType::Float;
+
+    wgpu::BindGroupLayoutDynamicBindingArray dynamic;
+    dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
+    dynamic.dynamicArray.start = 3;
+
+    wgpu::BindGroupLayoutDescriptor desc;
+    desc.nextInChain = &dynamic;
+    desc.entryCount = 1;
+    desc.entries = &entry;
+
+    // Control case: the non-arrayed static binding is before the dynamic array.
+    entry.binding = 2;
+    entry.bindingArraySize = 1;
+    device.CreateBindGroupLayout(&desc);
+
+    // Error case: the non-arrayed static binding is after the dynamic array.
+    entry.binding = 3;
+    entry.bindingArraySize = 1;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+
+    // Control case: the arrayed static binding is before the dynamic array.
+    entry.binding = 0;
+    entry.bindingArraySize = 3;
+    device.CreateBindGroupLayout(&desc);
+
+    // Error case: the arrayed static binding is after the dynamic array.
+    entry.binding = 0;
+    entry.bindingArraySize = 4;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
 }  // anonymous namespace
 }  // namespace dawn