Descriptorize BindGroups.

This commit adds utils::MakeBindGroup to make code craeting bind groups
nicer to read. Additional tests are added that give 100% coverage of
ValidateBindGroupDescriptor.

BUG=dawn:3

Change-Id: I56e1da8c2952306ad233845b0ec3ec32aef793d9
Reviewed-on: https://dawn-review.googlesource.com/c/2802
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 7f6a255..825ebb2 100644
--- a/dawn.json
+++ b/dawn.json
@@ -25,46 +25,23 @@
     "bind group": {
         "category": "object"
     },
-    "bind group builder": {
-        "category": "object",
-        "methods": [
-            {
-                "name": "get result",
-                "returns": "bind group"
-            },
-            {
-                "name": "set layout",
-                "args": [
-                    {"name": "layout", "type": "bind group layout"}
-                ]
-            },
-            {
-                "name": "set buffer views",
-                "args": [
-                    {"name": "start", "type": "uint32_t"},
-                    {"name": "count", "type": "uint32_t"},
-                    {"name": "buffer views", "type": "buffer view", "annotation": "const*", "length": "count"}
-                ]
-            },
-            {
-                "name": "set samplers",
-                "args": [
-                    {"name": "start", "type": "uint32_t"},
-                    {"name": "count", "type": "uint32_t"},
-                    {"name": "samplers", "type": "sampler", "annotation": "const*", "length": "count"}
-                ]
-            },
-            {
-                "name": "set texture views",
-                "args": [
-                    {"name": "start", "type": "uint32_t"},
-                    {"name": "count", "type": "uint32_t"},
-                    {"name": "texture views", "type": "texture view", "annotation": "const*", "length": "count"}
-                ]
-            }
-        ],
-        "TODO": [
-            "When resource are added, add methods for setting the content of the bind group"
+    "bind group binding": {
+        "category": "structure",
+        "extensible": false,
+        "members": [
+            {"name": "binding", "type": "uint32_t"},
+            {"name": "buffer view", "type": "buffer view", "optional": true},
+            {"name": "sampler", "type": "sampler", "optional": true},
+            {"name": "texture view", "type": "texture view", "optional": true}
+        ]
+    },
+    "bind group descriptor": {
+        "category": "structure",
+        "extensible": true,
+        "members": [
+            {"name": "layout", "type": "bind group layout"},
+            {"name": "num bindings", "type": "uint32_t"},
+            {"name": "bindings", "type": "bind group binding", "annotation": "const*", "length": "num bindings"}
         ]
     },
     "bind group layout": {
@@ -448,8 +425,11 @@
         "category": "object",
         "methods": [
             {
-                "name": "create bind group builder",
-                "returns": "bind group builder"
+                "name": "create bind group",
+                "returns": "bind group",
+                "args": [
+                    {"name": "descriptor", "type": "bind group descriptor", "annotation": "const*"}
+                ]
             },
             {
                 "name": "create bind group layout",
diff --git a/examples/ComputeBoids.cpp b/examples/ComputeBoids.cpp
index 869f95f..fb2e992 100644
--- a/examples/ComputeBoids.cpp
+++ b/examples/ComputeBoids.cpp
@@ -249,12 +249,11 @@
     }
 
     for (uint32_t i = 0; i < 2; ++i) {
-        updateBGs[i] = device.CreateBindGroupBuilder()
-            .SetLayout(bgl)
-            .SetBufferViews(0, 1, &updateParamsView)
-            .SetBufferViews(1, 1, &views[i])
-            .SetBufferViews(2, 1, &views[(i + 1) % 2])
-            .GetResult();
+        updateBGs[i] = utils::MakeBindGroup(device, bgl, {
+            {0, updateParamsView},
+            {1, views[i]},
+            {2, views[(i + 1) % 2]}
+        });
     }
 }
 
diff --git a/examples/CppHelloTriangle.cpp b/examples/CppHelloTriangle.cpp
index de60b2e..bf14e1a 100644
--- a/examples/CppHelloTriangle.cpp
+++ b/examples/CppHelloTriangle.cpp
@@ -137,11 +137,10 @@
 
     dawn::TextureView view = texture.CreateDefaultTextureView();
 
-    bindGroup = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetSamplers(0, 1, &sampler)
-        .SetTextureViews(1, 1, &view)
-        .GetResult();
+    bindGroup = utils::MakeBindGroup(device, bgl, {
+        {0, sampler},
+        {1, view}
+    });
 }
 
 struct {uint32_t a; float b;} s;
diff --git a/examples/CubeReflection.cpp b/examples/CubeReflection.cpp
index 5a5f658..9d38860 100644
--- a/examples/CubeReflection.cpp
+++ b/examples/CubeReflection.cpp
@@ -193,17 +193,15 @@
             .GetResult(),
     };
 
-    bindGroup[0] = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &cameraBufferView)
-        .SetBufferViews(1, 1, &transformBufferView[0])
-        .GetResult();
+    bindGroup[0] = utils::MakeBindGroup(device, bgl, {
+        {0, cameraBufferView},
+        {1, transformBufferView[0]}
+    });
 
-    bindGroup[1] = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &cameraBufferView)
-        .SetBufferViews(1, 1, &transformBufferView[1])
-        .GetResult();
+    bindGroup[1] = utils::MakeBindGroup(device, bgl, {
+        {0, cameraBufferView},
+        {1, transformBufferView[1]}
+    });
 
     depthStencilView = CreateDefaultDepthStencilView(device);
 
diff --git a/examples/glTFViewer/glTFViewer.cpp b/examples/glTFViewer/glTFViewer.cpp
index 873a246..9b02bb3 100644
--- a/examples/glTFViewer/glTFViewer.cpp
+++ b/examples/glTFViewer/glTFViewer.cpp
@@ -298,19 +298,22 @@
             .SetDepthStencilState(depthStencilState)
             .GetResult();
 
-        auto bindGroupBuilder = device.CreateBindGroupBuilder();
-        bindGroupBuilder.SetLayout(bindGroupLayout);
+        dawn::BindGroup bindGroup;
 
         if (hasTexture) {
             const auto& textureView = textures[iTextureID];
             const auto& iSamplerID = scene.textures[iTextureID].sampler;
-            bindGroupBuilder.SetSamplers(0, 1, &samplers[iSamplerID]);
-            bindGroupBuilder.SetTextureViews(1, 1, &textureView);
+            bindGroup = utils::MakeBindGroup(device, bindGroupLayout, {
+                {0, samplers[iSamplerID]},
+                {1, textureView}
+            });
+        } else {
+            bindGroup = utils::MakeBindGroup(device, bindGroupLayout, {});
         }
 
         MaterialInfo material = {
-            pipeline.Get(),
-            bindGroupBuilder.GetResult(),
+            pipeline,
+            bindGroup,
             std::map<uint32_t, std::string>(),
         };
         materials[key] = std::move(material);
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp
index 637b4b2..fa5ad8d 100644
--- a/src/dawn_native/BindGroup.cpp
+++ b/src/dawn_native/BindGroup.cpp
@@ -19,16 +19,147 @@
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Buffer.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/Sampler.h"
 #include "dawn_native/Texture.h"
 
 namespace dawn_native {
 
+    namespace {
+
+        // Helper functions to perform binding-type specific validation
+
+        MaybeError ValidateBufferBinding(const BindGroupBinding& binding,
+                                         dawn::BufferUsageBit requiredUsage) {
+            if (binding.bufferView == nullptr || binding.sampler != nullptr ||
+                binding.textureView != nullptr) {
+                return DAWN_VALIDATION_ERROR("expected buffer binding");
+            }
+
+            if (!IsAligned(binding.bufferView->GetOffset(), 256)) {
+                return DAWN_VALIDATION_ERROR(
+                    "Buffer view offset for bind group needs to be 256-byte aligned");
+            }
+
+            if (!(binding.bufferView->GetBuffer()->GetUsage() & requiredUsage)) {
+                return DAWN_VALIDATION_ERROR("buffer binding usage mismatch");
+            }
+
+            return {};
+        }
+
+        MaybeError ValidateTextureBinding(const BindGroupBinding& binding,
+                                          dawn::TextureUsageBit requiredUsage) {
+            if (binding.textureView == nullptr || binding.sampler != nullptr ||
+                binding.bufferView != nullptr) {
+                return DAWN_VALIDATION_ERROR("expected texture binding");
+            }
+
+            if (!(binding.textureView->GetTexture()->GetUsage() & requiredUsage)) {
+                return DAWN_VALIDATION_ERROR("texture binding usage mismatch");
+            }
+
+            return {};
+        }
+
+        MaybeError ValidateSamplerBinding(const BindGroupBinding& binding) {
+            if (binding.sampler == nullptr || binding.textureView != nullptr ||
+                binding.bufferView != nullptr) {
+                return DAWN_VALIDATION_ERROR("expected sampler binding");
+            }
+            return {};
+        }
+
+    }  // anonymous namespace
+
+    MaybeError ValidateBindGroupDescriptor(DeviceBase*, const BindGroupDescriptor* descriptor) {
+        if (descriptor->nextInChain != nullptr) {
+            return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
+        }
+
+        const BindGroupLayoutBase::LayoutBindingInfo& layoutInfo =
+            descriptor->layout->GetBindingInfo();
+
+        if (descriptor->numBindings != layoutInfo.mask.count()) {
+            return DAWN_VALIDATION_ERROR("numBindings mismatch");
+        }
+
+        std::bitset<kMaxBindingsPerGroup> bindingsSet;
+        for (uint32_t i = 0; i < descriptor->numBindings; ++i) {
+            const BindGroupBinding& binding = descriptor->bindings[i];
+            uint32_t bindingIndex = binding.binding;
+
+            // Check that we can set this binding.
+            if (bindingIndex >= kMaxBindingsPerGroup) {
+                return DAWN_VALIDATION_ERROR("binding index too high");
+            }
+
+            if (!layoutInfo.mask[bindingIndex]) {
+                return DAWN_VALIDATION_ERROR("setting non-existent binding");
+            }
+
+            if (bindingsSet[bindingIndex]) {
+                return DAWN_VALIDATION_ERROR("binding set twice");
+            }
+            bindingsSet.set(bindingIndex);
+
+            // Perform binding-type specific validation.
+            switch (layoutInfo.types[bindingIndex]) {
+                case dawn::BindingType::UniformBuffer:
+                    DAWN_TRY(ValidateBufferBinding(binding, dawn::BufferUsageBit::Uniform));
+                    break;
+                case dawn::BindingType::StorageBuffer:
+                    DAWN_TRY(ValidateBufferBinding(binding, dawn::BufferUsageBit::Storage));
+                    break;
+                case dawn::BindingType::SampledTexture:
+                    DAWN_TRY(ValidateTextureBinding(binding, dawn::TextureUsageBit::Sampled));
+                    break;
+                case dawn::BindingType::Sampler:
+                    DAWN_TRY(ValidateSamplerBinding(binding));
+                    break;
+            }
+        }
+
+        // This should always be true because
+        //  - numBindings has to match between the bind group and its layout.
+        //  - Each binding must be set at most once
+        //
+        // We don't validate the equality because it wouldn't be possible to cover it with a test.
+        ASSERT(bindingsSet == layoutInfo.mask);
+
+        return {};
+    }
+
     // BindGroup
 
-    BindGroupBase::BindGroupBase(BindGroupBuilder* builder)
-        : ObjectBase(builder->GetDevice()),
-          mLayout(std::move(builder->mLayout)),
-          mBindings(std::move(builder->mBindings)) {
+    BindGroupBase::BindGroupBase(DeviceBase* device, const BindGroupDescriptor* descriptor)
+        : ObjectBase(device), mLayout(descriptor->layout) {
+        for (uint32_t i = 0; i < descriptor->numBindings; ++i) {
+            const BindGroupBinding& binding = descriptor->bindings[i];
+
+            uint32_t bindingIndex = binding.binding;
+            ASSERT(bindingIndex < kMaxBindingsPerGroup);
+
+            // Only a single binding type should be set, so once we found it we can skip to the
+            // next loop iteration.
+
+            if (binding.bufferView != nullptr) {
+                ASSERT(mBindings[bindingIndex].Get() == nullptr);
+                mBindings[bindingIndex] = binding.bufferView;
+                continue;
+            }
+
+            if (binding.textureView != nullptr) {
+                ASSERT(mBindings[bindingIndex].Get() == nullptr);
+                mBindings[bindingIndex] = binding.textureView;
+                continue;
+            }
+
+            if (binding.sampler != nullptr) {
+                ASSERT(mBindings[bindingIndex].Get() == nullptr);
+                mBindings[bindingIndex] = binding.sampler;
+                continue;
+            }
+        }
     }
 
     const BindGroupLayoutBase* BindGroupBase::GetLayout() const {
@@ -56,154 +187,4 @@
         ASSERT(mLayout->GetBindingInfo().types[binding] == dawn::BindingType::SampledTexture);
         return reinterpret_cast<TextureViewBase*>(mBindings[binding].Get());
     }
-
-    // BindGroupBuilder
-
-    enum BindGroupSetProperties {
-        BINDGROUP_PROPERTY_LAYOUT = 0x1,
-    };
-
-    BindGroupBuilder::BindGroupBuilder(DeviceBase* device) : Builder(device) {
-    }
-
-    BindGroupBase* BindGroupBuilder::GetResultImpl() {
-        constexpr int allProperties = BINDGROUP_PROPERTY_LAYOUT;
-        if ((mPropertiesSet & allProperties) != allProperties) {
-            HandleError("Bindgroup missing properties");
-            return nullptr;
-        }
-
-        if (mSetMask != mLayout->GetBindingInfo().mask) {
-            HandleError("Bindgroup missing bindings");
-            return nullptr;
-        }
-
-        return GetDevice()->CreateBindGroup(this);
-    }
-
-    void BindGroupBuilder::SetLayout(BindGroupLayoutBase* layout) {
-        if ((mPropertiesSet & BINDGROUP_PROPERTY_LAYOUT) != 0) {
-            HandleError("Bindgroup layout property set multiple times");
-            return;
-        }
-
-        mLayout = layout;
-        mPropertiesSet |= BINDGROUP_PROPERTY_LAYOUT;
-    }
-
-    void BindGroupBuilder::SetBufferViews(uint32_t start,
-                                          uint32_t count,
-                                          BufferViewBase* const* bufferViews) {
-        if (!SetBindingsValidationBase(start, count)) {
-            return;
-        }
-
-        const auto& layoutInfo = mLayout->GetBindingInfo();
-        for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
-            dawn::BufferUsageBit requiredBit = dawn::BufferUsageBit::None;
-            switch (layoutInfo.types[i]) {
-                case dawn::BindingType::UniformBuffer:
-                    requiredBit = dawn::BufferUsageBit::Uniform;
-                    break;
-
-                case dawn::BindingType::StorageBuffer:
-                    requiredBit = dawn::BufferUsageBit::Storage;
-                    break;
-
-                case dawn::BindingType::Sampler:
-                case dawn::BindingType::SampledTexture:
-                    HandleError("Setting buffer for a wrong binding type");
-                    return;
-            }
-
-            if (!(bufferViews[j]->GetBuffer()->GetUsage() & requiredBit)) {
-                HandleError("Buffer needs to allow the correct usage bit");
-                return;
-            }
-
-            if (!IsAligned(bufferViews[j]->GetOffset(), 256)) {
-                HandleError("Buffer view offset for bind group needs to be 256-byte aligned");
-                return;
-            }
-        }
-
-        SetBindingsBase(start, count, reinterpret_cast<ObjectBase* const*>(bufferViews));
-    }
-
-    void BindGroupBuilder::SetSamplers(uint32_t start,
-                                       uint32_t count,
-                                       SamplerBase* const* samplers) {
-        if (!SetBindingsValidationBase(start, count)) {
-            return;
-        }
-
-        const auto& layoutInfo = mLayout->GetBindingInfo();
-        for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
-            if (layoutInfo.types[i] != dawn::BindingType::Sampler) {
-                HandleError("Setting binding for a wrong layout binding type");
-                return;
-            }
-        }
-
-        SetBindingsBase(start, count, reinterpret_cast<ObjectBase* const*>(samplers));
-    }
-
-    void BindGroupBuilder::SetTextureViews(uint32_t start,
-                                           uint32_t count,
-                                           TextureViewBase* const* textureViews) {
-        if (!SetBindingsValidationBase(start, count)) {
-            return;
-        }
-
-        const auto& layoutInfo = mLayout->GetBindingInfo();
-        for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
-            if (layoutInfo.types[i] != dawn::BindingType::SampledTexture) {
-                HandleError("Setting binding for a wrong layout binding type");
-                return;
-            }
-
-            if (!(textureViews[j]->GetTexture()->GetUsage() & dawn::TextureUsageBit::Sampled)) {
-                HandleError("Texture needs to allow the sampled usage bit");
-                return;
-            }
-        }
-
-        SetBindingsBase(start, count, reinterpret_cast<ObjectBase* const*>(textureViews));
-    }
-
-    void BindGroupBuilder::SetBindingsBase(uint32_t start,
-                                           uint32_t count,
-                                           ObjectBase* const* objects) {
-        for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
-            mSetMask.set(i);
-            mBindings[i] = objects[j];
-        }
-    }
-
-    bool BindGroupBuilder::SetBindingsValidationBase(uint32_t start, uint32_t count) {
-        if (start + count > kMaxBindingsPerGroup) {
-            HandleError("Setting bindings type over maximum number of bindings");
-            return false;
-        }
-
-        if ((mPropertiesSet & BINDGROUP_PROPERTY_LAYOUT) == 0) {
-            HandleError("Bindgroup layout must be set before views");
-            return false;
-        }
-
-        const auto& layoutInfo = mLayout->GetBindingInfo();
-        for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
-            if (mSetMask[i]) {
-                HandleError("Setting already set binding");
-                return false;
-            }
-
-            if (!layoutInfo.mask[i]) {
-                HandleError("Setting binding that isn't present in the layout");
-                return false;
-            }
-        }
-
-        return true;
-    }
 }  // namespace dawn_native
diff --git a/src/dawn_native/BindGroup.h b/src/dawn_native/BindGroup.h
index 115b825..b898d71 100644
--- a/src/dawn_native/BindGroup.h
+++ b/src/dawn_native/BindGroup.h
@@ -17,20 +17,24 @@
 
 #include "common/Constants.h"
 #include "dawn_native/BindGroupLayout.h"
-#include "dawn_native/Builder.h"
+#include "dawn_native/Error.h"
 #include "dawn_native/Forward.h"
 #include "dawn_native/ObjectBase.h"
 
 #include "dawn_native/dawn_platform.h"
 
 #include <array>
-#include <bitset>
 
 namespace dawn_native {
 
+    class DeviceBase;
+
+    MaybeError ValidateBindGroupDescriptor(DeviceBase* device,
+                                           const BindGroupDescriptor* descriptor);
+
     class BindGroupBase : public ObjectBase {
       public:
-        BindGroupBase(BindGroupBuilder* builder);
+        BindGroupBase(DeviceBase* device, const BindGroupDescriptor* descriptor);
 
         const BindGroupLayoutBase* GetLayout() const;
         BufferViewBase* GetBindingAsBufferView(size_t binding);
@@ -42,31 +46,6 @@
         std::array<Ref<ObjectBase>, kMaxBindingsPerGroup> mBindings;
     };
 
-    class BindGroupBuilder : public Builder<BindGroupBase> {
-      public:
-        BindGroupBuilder(DeviceBase* device);
-
-        // Dawn API
-        void SetLayout(BindGroupLayoutBase* layout);
-
-        void SetBufferViews(uint32_t start, uint32_t count, BufferViewBase* const* bufferViews);
-        void SetSamplers(uint32_t start, uint32_t count, SamplerBase* const* samplers);
-        void SetTextureViews(uint32_t start, uint32_t count, TextureViewBase* const* textureViews);
-
-      private:
-        friend class BindGroupBase;
-
-        BindGroupBase* GetResultImpl() override;
-        void SetBindingsBase(uint32_t start, uint32_t count, ObjectBase* const* objects);
-        bool SetBindingsValidationBase(uint32_t start, uint32_t count);
-
-        std::bitset<kMaxBindingsPerGroup> mSetMask;
-        int mPropertiesSet = 0;
-
-        Ref<BindGroupLayoutBase> mLayout;
-        std::array<Ref<ObjectBase>, kMaxBindingsPerGroup> mBindings;
-    };
-
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_BINDGROUP_H_
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 0ab1323..6a861fa 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -101,8 +101,14 @@
 
     // Object creation API methods
 
-    BindGroupBuilder* DeviceBase::CreateBindGroupBuilder() {
-        return new BindGroupBuilder(this);
+    BindGroupBase* DeviceBase::CreateBindGroup(const BindGroupDescriptor* descriptor) {
+        BindGroupBase* result = nullptr;
+
+        if (ConsumedError(CreateBindGroupInternal(&result, descriptor))) {
+            return nullptr;
+        }
+
+        return result;
     }
     BindGroupLayoutBase* DeviceBase::CreateBindGroupLayout(
         const BindGroupLayoutDescriptor* descriptor) {
@@ -240,6 +246,13 @@
 
     // Implementation details of object creation
 
+    MaybeError DeviceBase::CreateBindGroupInternal(BindGroupBase** result,
+                                                   const BindGroupDescriptor* descriptor) {
+        DAWN_TRY(ValidateBindGroupDescriptor(this, descriptor));
+        DAWN_TRY_ASSIGN(*result, CreateBindGroupImpl(descriptor));
+        return {};
+    }
+
     MaybeError DeviceBase::CreateBindGroupLayoutInternal(
         BindGroupLayoutBase** result,
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index cbb5af6..87e5e29 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -51,7 +51,6 @@
 
         FenceSignalTracker* GetFenceSignalTracker() const;
 
-        virtual BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) = 0;
         virtual BlendStateBase* CreateBlendState(BlendStateBuilder* builder) = 0;
         virtual BufferViewBase* CreateBufferView(BufferViewBuilder* builder) = 0;
         virtual CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) = 0;
@@ -86,7 +85,7 @@
         void UncacheBindGroupLayout(BindGroupLayoutBase* obj);
 
         // Dawn API
-        BindGroupBuilder* CreateBindGroupBuilder();
+        BindGroupBase* CreateBindGroup(const BindGroupDescriptor* descriptor);
         BindGroupLayoutBase* CreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor);
         BlendStateBuilder* CreateBlendStateBuilder();
         BufferBase* CreateBuffer(const BufferDescriptor* descriptor);
@@ -119,6 +118,8 @@
         virtual const PCIInfo& GetPCIInfo() const = 0;
 
       private:
+        virtual ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) = 0;
         virtual ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) = 0;
         virtual ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) = 0;
@@ -137,6 +138,8 @@
             TextureBase* texture,
             const TextureViewDescriptor* descriptor) = 0;
 
+        MaybeError CreateBindGroupInternal(BindGroupBase** result,
+                                           const BindGroupDescriptor* descriptor);
         MaybeError CreateBindGroupLayoutInternal(BindGroupLayoutBase** result,
                                                  const BindGroupLayoutDescriptor* descriptor);
         MaybeError CreateBufferInternal(BufferBase** result, const BufferDescriptor* descriptor);
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp
index c013d80..586f05b 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp
@@ -23,7 +23,8 @@
 
 namespace dawn_native { namespace d3d12 {
 
-    BindGroup::BindGroup(BindGroupBuilder* builder) : BindGroupBase(builder) {
+    BindGroup::BindGroup(Device* device, const BindGroupDescriptor* descriptor)
+        : BindGroupBase(device, descriptor) {
     }
 
     void BindGroup::RecordDescriptors(const DescriptorHeapHandle& cbvUavSrvHeapStart,
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.h b/src/dawn_native/d3d12/BindGroupD3D12.h
index b577939..f8d5fbf 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.h
+++ b/src/dawn_native/d3d12/BindGroupD3D12.h
@@ -27,7 +27,7 @@
 
     class BindGroup : public BindGroupBase {
       public:
-        BindGroup(BindGroupBuilder* builder);
+        BindGroup(Device* device, const BindGroupDescriptor* descriptor);
 
         void RecordDescriptors(const DescriptorHeapHandle& cbvSrvUavHeapStart,
                                uint32_t* cbvUavSrvHeapOffset,
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 6d47920..e225e38 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -286,8 +286,9 @@
         }
     }
 
-    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
-        return new BindGroup(builder);
+    ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
+        const BindGroupDescriptor* descriptor) {
+        return new BindGroup(this, descriptor);
     }
     ResultOrError<BindGroupLayoutBase*> Device::CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index dfed566..340093e 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -41,7 +41,6 @@
         Device();
         ~Device();
 
-        BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
@@ -80,6 +79,8 @@
         void ExecuteCommandLists(std::initializer_list<ID3D12CommandList*> commandLists);
 
       private:
+        ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) override;
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) override;
         ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) override;
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index 5833588..bba267c 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -37,7 +37,6 @@
         Device(id<MTLDevice> mtlDevice);
         ~Device();
 
-        BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
@@ -64,6 +63,8 @@
         ResourceUploader* GetResourceUploader() const;
 
       private:
+        ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) override;
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) override;
         ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) override;
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 0484e7a..2c49036 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -162,8 +162,9 @@
         mCommandQueue = nil;
     }
 
-    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
-        return new BindGroup(builder);
+    ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
+        const BindGroupDescriptor* descriptor) {
+        return new BindGroup(this, descriptor);
     }
     ResultOrError<BindGroupLayoutBase*> Device::CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/null/NullBackend.cpp b/src/dawn_native/null/NullBackend.cpp
index 9512f0f..1ee9973 100644
--- a/src/dawn_native/null/NullBackend.cpp
+++ b/src/dawn_native/null/NullBackend.cpp
@@ -34,8 +34,9 @@
     Device::~Device() {
     }
 
-    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
-        return new BindGroup(builder);
+    ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
+        const BindGroupDescriptor* descriptor) {
+        return new BindGroup(this, descriptor);
     }
     ResultOrError<BindGroupLayoutBase*> Device::CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/null/NullBackend.h b/src/dawn_native/null/NullBackend.h
index b77f6cf..edaea8a 100644
--- a/src/dawn_native/null/NullBackend.h
+++ b/src/dawn_native/null/NullBackend.h
@@ -95,7 +95,6 @@
         Device();
         ~Device();
 
-        BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
@@ -116,6 +115,8 @@
         void SubmitPendingOperations();
 
       private:
+        ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) override;
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) override;
         ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) override;
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index e58fb02..22b13da 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -61,8 +61,9 @@
         Tick();
     }
 
-    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
-        return new BindGroup(builder);
+    ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
+        const BindGroupDescriptor* descriptor) {
+        return new BindGroup(this, descriptor);
     }
     ResultOrError<BindGroupLayoutBase*> Device::CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index 90b14ac..7471bd2 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -40,7 +40,6 @@
         void SubmitFenceSync();
 
         // Dawn API
-        BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
@@ -58,6 +57,8 @@
         const dawn_native::PCIInfo& GetPCIInfo() const override;
 
       private:
+        ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) override;
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) override;
         ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) override;
diff --git a/src/dawn_native/vulkan/BindGroupVk.cpp b/src/dawn_native/vulkan/BindGroupVk.cpp
index 49a972c..461a13e 100644
--- a/src/dawn_native/vulkan/BindGroupVk.cpp
+++ b/src/dawn_native/vulkan/BindGroupVk.cpp
@@ -25,7 +25,8 @@
 
 namespace dawn_native { namespace vulkan {
 
-    BindGroup::BindGroup(BindGroupBuilder* builder) : BindGroupBase(builder) {
+    BindGroup::BindGroup(Device* device, const BindGroupDescriptor* descriptor)
+        : BindGroupBase(device, descriptor) {
         // Create a pool to hold our descriptor set.
         // TODO(cwallez@chromium.org): This horribly inefficient, find a way to be better, for
         // example by having one pool per bind group layout instead.
@@ -40,7 +41,6 @@
         createInfo.poolSizeCount = numPoolSizes;
         createInfo.pPoolSizes = poolSizes.data();
 
-        Device* device = ToBackend(GetDevice());
         if (device->fn.CreateDescriptorPool(device->GetVkDevice(), &createInfo, nullptr, &mPool) !=
             VK_SUCCESS) {
             ASSERT(false);
diff --git a/src/dawn_native/vulkan/BindGroupVk.h b/src/dawn_native/vulkan/BindGroupVk.h
index 9dee3a3..5071796 100644
--- a/src/dawn_native/vulkan/BindGroupVk.h
+++ b/src/dawn_native/vulkan/BindGroupVk.h
@@ -25,7 +25,7 @@
 
     class BindGroup : public BindGroupBase {
       public:
-        BindGroup(BindGroupBuilder* builder);
+        BindGroup(Device* device, const BindGroupDescriptor* descriptor);
         ~BindGroup();
 
         VkDescriptorSet GetHandle() const;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 0ba4c31..2f3bb88 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -212,8 +212,9 @@
         }
     }
 
-    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
-        return new BindGroup(builder);
+    ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
+        const BindGroupDescriptor* descriptor) {
+        return new BindGroup(this, descriptor);
     }
     ResultOrError<BindGroupLayoutBase*> Device::CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) {
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index ae1f940..8cf7ece 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -63,7 +63,6 @@
         void AddWaitSemaphore(VkSemaphore semaphore);
 
         // Dawn API
-        BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
@@ -81,6 +80,8 @@
         const dawn_native::PCIInfo& GetPCIInfo() const override;
 
       private:
+        ResultOrError<BindGroupBase*> CreateBindGroupImpl(
+            const BindGroupDescriptor* descriptor) override;
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
             const BindGroupLayoutDescriptor* descriptor) override;
         ResultOrError<BufferBase*> CreateBufferImpl(const BufferDescriptor* descriptor) override;
diff --git a/src/tests/end2end/BindGroupTests.cpp b/src/tests/end2end/BindGroupTests.cpp
index 42dd704..6dcbf32 100644
--- a/src/tests/end2end/BindGroupTests.cpp
+++ b/src/tests/end2end/BindGroupTests.cpp
@@ -68,10 +68,7 @@
     dawn::Buffer buffer = device.CreateBuffer(&bufferDesc);
     dawn::BufferView bufferView =
         buffer.CreateBufferViewBuilder().SetExtent(0, sizeof(float)).GetResult();
-    dawn::BindGroup bindGroup = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &bufferView)
-        .GetResult();
+    dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, bufferView}});
 
     dawn::CommandBuffer cb[2];
     cb[0] = CreateSimpleComputeCommandBuffer(cp, bindGroup);
@@ -143,11 +140,10 @@
         buffer.CreateBufferViewBuilder().SetExtent(0, sizeof(Data::transform)).GetResult();
     dawn::BufferView fragUBOBufferView =
         buffer.CreateBufferViewBuilder().SetExtent(256, sizeof(Data::color)).GetResult();
-    dawn::BindGroup bindGroup = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &vertUBOBufferView)
-        .SetBufferViews(1, 1, &fragUBOBufferView)
-        .GetResult();
+    dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {
+        {0, vertUBOBufferView},
+        {1, fragUBOBufferView}
+    });
 
     dawn::CommandBufferBuilder builder = device.CreateCommandBufferBuilder();
     dawn::RenderPassEncoder pass = builder.BeginRenderPass(renderPass.renderPassInfo);
@@ -250,12 +246,11 @@
         data[i] = RGBA8(0, 255, 0, 255);
     }
     dawn::Buffer stagingBuffer = utils::CreateBufferFromData(device, data.data(), sizeInBytes, dawn::BufferUsageBit::TransferSrc);
-    dawn::BindGroup bindGroup = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &vertUBOBufferView)
-        .SetSamplers(1, 1, &sampler)
-        .SetTextureViews(2, 1, &textureView)
-        .GetResult();
+    dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {
+        {0, vertUBOBufferView},
+        {1, sampler},
+        {2, textureView}
+    });
 
     dawn::CommandBufferBuilder builder = device.CreateCommandBufferBuilder();
     dawn::BufferCopyView bufferCopyView =
diff --git a/src/tests/end2end/BlendStateTests.cpp b/src/tests/end2end/BlendStateTests.cpp
index 1e38597..468b434 100644
--- a/src/tests/end2end/BlendStateTests.cpp
+++ b/src/tests/end2end/BlendStateTests.cpp
@@ -101,10 +101,7 @@
                 .SetExtent(0, bufferSize)
                 .GetResult();
 
-            return device.CreateBindGroupBuilder()
-                .SetLayout(bindGroupLayout)
-                .SetBufferViews(0, 1, &view)
-                .GetResult();
+            return utils::MakeBindGroup(device, bindGroupLayout, {{0, view}});
         }
 
         // Test that after drawing a triangle with the base color, and then the given triangle spec, the color is as expected
diff --git a/src/tests/end2end/ComputeCopyStorageBufferTests.cpp b/src/tests/end2end/ComputeCopyStorageBufferTests.cpp
index 72a34fb..b156e4b 100644
--- a/src/tests/end2end/ComputeCopyStorageBufferTests.cpp
+++ b/src/tests/end2end/ComputeCopyStorageBufferTests.cpp
@@ -73,11 +73,10 @@
         dst.CreateBufferViewBuilder().SetExtent(0, kNumUints * sizeof(uint32_t)).GetResult();
 
     // Set up bind group and issue dispatch
-    auto bindGroup = device.CreateBindGroupBuilder()
-                         .SetLayout(bgl)
-                         .SetBufferViews(0, 1, &srcView)
-                         .SetBufferViews(1, 1, &dstView)
-                         .GetResult();
+    dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {
+        {0, srcView},
+        {1, dstView},
+    });
 
     dawn::CommandBuffer commands;
     {
diff --git a/src/tests/end2end/DepthStencilStateTests.cpp b/src/tests/end2end/DepthStencilStateTests.cpp
index 0fe628e..be932a4 100644
--- a/src/tests/end2end/DepthStencilStateTests.cpp
+++ b/src/tests/end2end/DepthStencilStateTests.cpp
@@ -214,10 +214,7 @@
                     .GetResult();
 
                 // Create a bind group for the data
-                dawn::BindGroup bindGroup = device.CreateBindGroupBuilder()
-                    .SetLayout(bindGroupLayout)
-                    .SetBufferViews(0, 1, &view)
-                    .GetResult();
+                dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bindGroupLayout, {{0, view}});
 
                 // Create a pipeline for the triangles with the test spec's depth stencil state
                 dawn::RenderPipeline pipeline = device.CreateRenderPipelineBuilder()
diff --git a/src/tests/end2end/PushConstantTests.cpp b/src/tests/end2end/PushConstantTests.cpp
index e9bd05e..3cac2c6 100644
--- a/src/tests/end2end/PushConstantTests.cpp
+++ b/src/tests/end2end/PushConstantTests.cpp
@@ -56,10 +56,17 @@
                 buf2.CreateBufferViewBuilder().SetExtent(0, 4).GetResult(),
             };
 
-            dawn::BindGroup bg = device.CreateBindGroupBuilder()
-                .SetLayout(bgl)
-                .SetBufferViews(0, extraBuffer ? 2 : 1, views)
-                .GetResult();
+            dawn::BindGroup bg;
+            if (extraBuffer) {
+                bg = utils::MakeBindGroup(device, bgl, {
+                    {0, views[0]},
+                    {1, views[1]},
+                });
+            } else {
+                bg = utils::MakeBindGroup(device, bgl, {
+                    {0, views[0]},
+                });
+            }
 
             return {std::move(pl), std::move(bg), std::move(buf1)};
         }
diff --git a/src/tests/end2end/SamplerTests.cpp b/src/tests/end2end/SamplerTests.cpp
index 9de5d63..2bdad06 100644
--- a/src/tests/end2end/SamplerTests.cpp
+++ b/src/tests/end2end/SamplerTests.cpp
@@ -126,11 +126,10 @@
             sampler = device.CreateSampler(&descriptor);
         }
 
-        auto bindGroup = device.CreateBindGroupBuilder()
-            .SetLayout(mBindGroupLayout)
-            .SetSamplers(0, 1, &sampler)
-            .SetTextureViews(1, 1, &mTextureView)
-            .GetResult();
+        dawn::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout, {
+            {0, sampler},
+            {1, mTextureView}
+        });
 
         dawn::CommandBufferBuilder builder = device.CreateCommandBufferBuilder();
         {
diff --git a/src/tests/end2end/TextureViewTests.cpp b/src/tests/end2end/TextureViewTests.cpp
index 98926a1..01f2a00 100644
--- a/src/tests/end2end/TextureViewTests.cpp
+++ b/src/tests/end2end/TextureViewTests.cpp
@@ -155,11 +155,10 @@
     }
 
     void Verify(const dawn::TextureView &textureView, const char* fragmentShader, int expected) {
-        dawn::BindGroup bindGroup = device.CreateBindGroupBuilder()
-            .SetLayout(mBindGroupLayout)
-            .SetSamplers(0, 1, &mSampler)
-            .SetTextureViews(1, 1, &textureView)
-            .GetResult();
+        dawn::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout, {
+            {0, mSampler},
+            {1, textureView}
+        });
 
         dawn::ShaderModule fsModule =
             utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, fragmentShader);
diff --git a/src/tests/unittests/WireTests.cpp b/src/tests/unittests/WireTests.cpp
index b8f3002..be522cb 100644
--- a/src/tests/unittests/WireTests.cpp
+++ b/src/tests/unittests/WireTests.cpp
@@ -494,21 +494,39 @@
 }
 
 // Test passing nullptr instead of objects - object as value version
-TEST_F(WireTests, DISABLED_NullptrAsValue) {
-    dawnCommandBufferBuilder builder = dawnDeviceCreateCommandBufferBuilder(device);
-    dawnComputePassEncoder pass = dawnCommandBufferBuilderBeginComputePass(builder);
-    dawnComputePassEncoderSetComputePipeline(pass, nullptr);
+TEST_F(WireTests, OptionalObjectValue) {
+    dawnBindGroupLayoutDescriptor bglDesc;
+    bglDesc.nextInChain = nullptr;
+    bglDesc.numBindings = 0;
+    dawnBindGroupLayout bgl = dawnDeviceCreateBindGroupLayout(device, &bglDesc);
 
-    dawnCommandBufferBuilder apiBuilder = api.GetNewCommandBufferBuilder();
-    EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
-        .WillOnce(Return(apiBuilder));
+    dawnBindGroupLayout apiBindGroupLayout = api.GetNewBindGroupLayout();
+    EXPECT_CALL(api, DeviceCreateBindGroupLayout(apiDevice, _))
+        .WillOnce(Return(apiBindGroupLayout));
 
-    dawnComputePassEncoder apiPass = api.GetNewComputePassEncoder();
-    EXPECT_CALL(api, CommandBufferBuilderBeginComputePass(apiBuilder))
-        .WillOnce(Return(apiPass));
+    // The `sampler`, `textureView` and `bufferView` members of a binding are optional.
+    dawnBindGroupBinding binding;
+    binding.binding = 0;
+    binding.sampler = nullptr;
+    binding.textureView = nullptr;
+    binding.bufferView = nullptr;
 
-    EXPECT_CALL(api, ComputePassEncoderSetComputePipeline(apiPass, nullptr))
-        .Times(1);
+    dawnBindGroupDescriptor bgDesc;
+    bgDesc.nextInChain = nullptr;
+    bgDesc.layout = bgl;
+    bgDesc.numBindings = 1;
+    bgDesc.bindings = &binding;
+
+    dawnDeviceCreateBindGroup(device, &bgDesc);
+    EXPECT_CALL(api, DeviceCreateBindGroup(apiDevice, MatchesLambda([](const dawnBindGroupDescriptor* desc) -> bool {
+        return desc->nextInChain == nullptr &&
+            desc->numBindings == 1 &&
+            desc->bindings[0].binding == 0 &&
+            desc->bindings[0].sampler == nullptr &&
+            desc->bindings[0].bufferView == nullptr &&
+            desc->bindings[0].textureView == nullptr;
+    })))
+        .WillOnce(Return(nullptr));
 
     FlushClient();
 }
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp
index 2937987..ddf981d 100644
--- a/src/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -18,98 +18,350 @@
 #include "utils/DawnHelpers.h"
 
 class BindGroupValidationTest : public ValidationTest {
+  public:
+    void SetUp() override {
+        // Create objects to use as resources inside test bind groups.
+        {
+            dawn::BufferDescriptor descriptor;
+            descriptor.size = 1024;
+            descriptor.usage = dawn::BufferUsageBit::Uniform;
+            mUBO = device.CreateBuffer(&descriptor);
+        }
+        {
+            dawn::BufferDescriptor descriptor;
+            descriptor.size = 1024;
+            descriptor.usage = dawn::BufferUsageBit::Storage;
+            mSSBO = device.CreateBuffer(&descriptor);
+        }
+        {
+            dawn::SamplerDescriptor descriptor = utils::GetDefaultSamplerDescriptor();
+            mSampler = device.CreateSampler(&descriptor);
+        }
+        {
+            dawn::TextureDescriptor descriptor;
+            descriptor.dimension = dawn::TextureDimension::e2D;
+            descriptor.size = {16, 16, 1};
+            descriptor.arrayLayer = 1;
+            descriptor.format = dawn::TextureFormat::R8G8B8A8Unorm;
+            descriptor.levelCount = 1;
+            descriptor.usage = dawn::TextureUsageBit::Sampled;
+            mSampledTexture = device.CreateTexture(&descriptor);
+            mSampledTextureView = mSampledTexture.CreateDefaultTextureView();
+        }
+    }
+
+  protected:
+    dawn::Buffer mUBO;
+    dawn::Buffer mSSBO;
+    dawn::Sampler mSampler;
+    dawn::Texture mSampledTexture;
+    dawn::TextureView mSampledTextureView;
 };
 
+// Test the validation of BindGroupDescriptor::nextInChain
+TEST_F(BindGroupValidationTest, NextInChainNullptr) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {});
+
+    dawn::BindGroupDescriptor descriptor;
+    descriptor.layout = layout;
+    descriptor.numBindings = 0;
+    descriptor.bindings = nullptr;
+
+    // Control case: check that nextInChain = nullptr is valid
+    descriptor.nextInChain = nullptr;
+    device.CreateBindGroup(&descriptor);
+
+    // Check that nextInChain != nullptr is an error.
+    descriptor.nextInChain = static_cast<void*>(&descriptor);
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+}
+
+// Check constraints on numBindings
+TEST_F(BindGroupValidationTest, NumBindingsMismatch) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler}
+    });
+
+    // Control case: check that a descriptor with one binding is ok
+    utils::MakeBindGroup(device, layout, {{0, mSampler}});
+
+    // Check that numBindings != layout.numBindings fails.
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {}));
+}
+
+// Check constraints on BindGroupBinding::binding
+TEST_F(BindGroupValidationTest, WrongBindings) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler}
+    });
+
+    // Control case: check that a descriptor with a binding matching the layout's is ok
+    utils::MakeBindGroup(device, layout, {{0, mSampler}});
+
+    // Check that binding must be present in the layout
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{1, mSampler}}));
+
+    // Check that binding >= kMaxBindingsPerGroup fails.
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{kMaxBindingsPerGroup, mSampler}}));
+}
+
+// Check that the same binding cannot be set twice
+TEST_F(BindGroupValidationTest, BindingSetTwice) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler},
+        {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler}
+    });
+
+    // Control case: check that different bindings work
+    utils::MakeBindGroup(device, layout, {
+        {0, mSampler},
+        {1, mSampler}
+    });
+
+    // Check that setting the same binding twice is invalid
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {
+        {0, mSampler},
+        {0, mSampler}
+    }));
+}
+
+// Check that a sampler binding must contain exactly one sampler
+TEST_F(BindGroupValidationTest, SamplerBindingType) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler}
+    });
+
+    dawn::BindGroupBinding binding;
+    binding.binding = 0;
+    binding.sampler = nullptr;
+    binding.textureView = nullptr;
+    binding.bufferView = nullptr;
+
+    dawn::BindGroupDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    descriptor.layout = layout;
+    descriptor.numBindings = 1;
+    descriptor.bindings = &binding;
+
+    // Not setting anything fails
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+
+    // Control case: setting just the sampler works
+    binding.sampler = mSampler;
+    device.CreateBindGroup(&descriptor);
+
+    // Setting the texture view as well is an error
+    binding.textureView = mSampledTextureView;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.textureView = nullptr;
+
+    // Setting the buffer view as well is an error
+    binding.bufferView = mUBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.bufferView = nullptr;
+}
+
+// Check that a texture binding must contain exactly a texture view
+TEST_F(BindGroupValidationTest, TextureBindingType) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture}
+    });
+
+    dawn::BindGroupBinding binding;
+    binding.binding = 0;
+    binding.sampler = nullptr;
+    binding.textureView = nullptr;
+    binding.bufferView = nullptr;
+
+    dawn::BindGroupDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    descriptor.layout = layout;
+    descriptor.numBindings = 1;
+    descriptor.bindings = &binding;
+
+    // Not setting anything fails
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+
+    // Control case: setting just the texture view works
+    binding.textureView = mSampledTextureView;
+    device.CreateBindGroup(&descriptor);
+
+    // Setting the sampler as well is an error
+    binding.sampler = mSampler;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.textureView = nullptr;
+
+    // Setting the buffer view as well is an error
+    binding.bufferView = mUBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.bufferView = nullptr;
+}
+
+// Check that a buffer binding must contain exactly a buffer view
+TEST_F(BindGroupValidationTest, BufferBindingType) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer}
+    });
+
+    dawn::BindGroupBinding binding;
+    binding.binding = 0;
+    binding.sampler = nullptr;
+    binding.textureView = nullptr;
+    binding.bufferView = nullptr;
+
+    dawn::BindGroupDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    descriptor.layout = layout;
+    descriptor.numBindings = 1;
+    descriptor.bindings = &binding;
+
+    // Not setting anything fails
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+
+    // Control case: setting just the buffer view works
+    binding.bufferView = mUBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+    device.CreateBindGroup(&descriptor);
+
+    // Setting the texture view as well is an error
+    binding.textureView = mSampledTextureView;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.textureView = nullptr;
+
+    // Setting the sampler as well is an error
+    binding.sampler = mSampler;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    binding.sampler = nullptr;
+}
+
+// Check that a texture must have the correct usage
+TEST_F(BindGroupValidationTest, TextureUsage) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture}
+    });
+
+    // Control case: setting a sampleable texture view works.
+    utils::MakeBindGroup(device, layout, {{0, mSampledTextureView}});
+
+    // Make an output attachment texture and try to set it for a SampledTexture binding
+    dawn::TextureDescriptor descriptor;
+    descriptor.dimension = dawn::TextureDimension::e2D;
+    descriptor.size = {16, 16, 1};
+    descriptor.arrayLayer = 1;
+    descriptor.format = dawn::TextureFormat::R8G8B8A8Unorm;
+    descriptor.levelCount = 1;
+    descriptor.usage = dawn::TextureUsageBit::OutputAttachment;
+    dawn::Texture outputTexture = device.CreateTexture(&descriptor);
+    dawn::TextureView outputTextureView = outputTexture.CreateDefaultTextureView();
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, outputTextureView}}));
+}
+
+// Check that a UBO must have the correct usage
+TEST_F(BindGroupValidationTest, BufferUsageUBO) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer}
+    });
+
+    dawn::BufferView uboView = mUBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+    dawn::BufferView ssboView = mSSBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+
+    // Control case: using a buffer with the uniform usage works
+    utils::MakeBindGroup(device, layout, {{0, uboView}});
+
+    // Using a buffer without the uniform usage fails
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, ssboView}}));
+}
+
+// Check that a SSBO must have the correct usage
+TEST_F(BindGroupValidationTest, BufferUsageSSBO) {
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::StorageBuffer}
+    });
+
+    dawn::BufferView uboView = mUBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+    dawn::BufferView ssboView = mSSBO.CreateBufferViewBuilder().SetExtent(0, 256).GetResult();
+
+    // Control case: using a buffer with the storage usage works
+    utils::MakeBindGroup(device, layout, {{0, ssboView}});
+
+    // Using a buffer without the storage usage fails
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, uboView}}));
+}
+
 // Tests constraints on the buffer view offset for bind groups.
 TEST_F(BindGroupValidationTest, BufferViewOffset) {
-    auto layout = utils::MakeBindGroupLayout(
-        device, {
-                    {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
-                });
-
-    dawn::Buffer buffer;
-    {
-        dawn::BufferDescriptor descriptor;
-        descriptor.size = 512;
-        descriptor.usage = dawn::BufferUsageBit::Uniform;
-        buffer = device.CreateBuffer(&descriptor);
-    }
+    dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+    });
 
     // Check that offset 0 is valid
     {
-        auto bufferView = buffer.CreateBufferViewBuilder()
+        dawn::BufferView bufferView = mUBO.CreateBufferViewBuilder()
             .SetExtent(0, 512)
             .GetResult();
-
-        auto bindGroup = AssertWillBeSuccess(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
+        utils::MakeBindGroup(device, layout, {{0, bufferView}});
     }
 
     // Check that offset 256 (aligned) is valid
     {
-        auto bufferView = buffer.CreateBufferViewBuilder()
+        dawn::BufferView bufferView = mUBO.CreateBufferViewBuilder()
             .SetExtent(256, 256)
             .GetResult();
-
-        auto bindGroup = AssertWillBeSuccess(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
+        utils::MakeBindGroup(device, layout, {{0, bufferView}});
     }
 
     // Check cases where unaligned buffer view offset is invalid
     {
-        auto bufferView = buffer.CreateBufferViewBuilder()
+        dawn::BufferView bufferView = mUBO.CreateBufferViewBuilder()
             .SetExtent(1, 256)
             .GetResult();
-
-        auto bindGroup = AssertWillBeError(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
+        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, bufferView}}));
     }
-
     {
-        auto bufferView = buffer.CreateBufferViewBuilder()
-            .SetExtent(64, 256)
-            .GetResult();
-
-        auto bindGroup = AssertWillBeError(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
-    }
-
-    {
-        auto bufferView = buffer.CreateBufferViewBuilder()
+        dawn::BufferView bufferView = mUBO.CreateBufferViewBuilder()
             .SetExtent(128, 256)
             .GetResult();
-
-        auto bindGroup = AssertWillBeError(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
+        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, bufferView}}));
     }
-
     {
-        auto bufferView = buffer.CreateBufferViewBuilder()
+        dawn::BufferView bufferView = mUBO.CreateBufferViewBuilder()
             .SetExtent(255, 256)
             .GetResult();
-
-        auto bindGroup = AssertWillBeError(device.CreateBindGroupBuilder())
-            .SetLayout(layout)
-            .SetBufferViews(0, 1, &bufferView)
-            .GetResult();
+        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, bufferView}}));
     }
 }
 
+class BindGroupLayoutValidationTest : public ValidationTest {
+};
+
+// Tests setting OOB checks for kMaxBindingsPerGroup in bind group layouts.
+TEST_F(BindGroupLayoutValidationTest, BindGroupLayoutBindingOOB) {
+    // Checks that kMaxBindingsPerGroup - 1 is valid.
+    utils::MakeBindGroupLayout(device, {
+        {kMaxBindingsPerGroup - 1, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}
+    });
+
+    // Checks that kMaxBindingsPerGroup is OOB
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(device, {
+        {kMaxBindingsPerGroup, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}
+    }));
+}
+
+// This test verifies that the BindGroupLayout bindings are correctly validated, even if the
+// binding ids are out-of-order.
+TEST_F(BindGroupLayoutValidationTest, BindGroupBinding) {
+    auto layout = utils::MakeBindGroupLayout(
+        device, {
+                    {1, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+                    {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+                });
+}
+
+
 // This test verifies that the BindGroupLayout cache is successfully caching/deduplicating objects.
 //
 // NOTE: This test only works currently because unittests are run without the wire - so the returned
 // BindGroupLayout pointers are actually visibly equivalent. With the wire, this would not be true.
-TEST_F(BindGroupValidationTest, BindGroupLayoutCache) {
+TEST_F(BindGroupLayoutValidationTest, BindGroupLayoutCache) {
     auto layout1 = utils::MakeBindGroupLayout(
         device, {
                     {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
@@ -122,26 +374,3 @@
     // Caching should cause these to be the same.
     ASSERT_EQ(layout1.Get(), layout2.Get());
 }
-
-// This test verifies that the BindGroupLayout bindings are correctly validated, even if the
-// binding ids are out-of-order.
-TEST_F(BindGroupValidationTest, BindGroupBinding) {
-    auto layout = utils::MakeBindGroupLayout(
-        device, {
-                    {1, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
-                    {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
-                });
-}
-
-// Tests setting OOB checks for kMaxBindingsPerGroup in bind group layouts.
-TEST_F(BindGroupValidationTest, BindGroupLayoutBindingOOB) {
-    // Checks that kMaxBindingsPerGroup - 1 is valid.
-    utils::MakeBindGroupLayout(device, {
-        {kMaxBindingsPerGroup - 1, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}
-    });
-
-    // Checks that kMaxBindingsPerGroup is OOB
-    ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(device, {
-        {kMaxBindingsPerGroup, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}
-    }));
-}
diff --git a/src/tests/unittests/validation/CommandBufferValidationTests.cpp b/src/tests/unittests/validation/CommandBufferValidationTests.cpp
index 62eba40..c0a62e3 100644
--- a/src/tests/unittests/validation/CommandBufferValidationTests.cpp
+++ b/src/tests/unittests/validation/CommandBufferValidationTests.cpp
@@ -75,10 +75,7 @@
         0, dawn::ShaderStageBit::Vertex, dawn::BindingType::StorageBuffer
     }});
     dawn::BufferView view = buffer.CreateBufferViewBuilder().SetExtent(0, 4).GetResult();
-    dawn::BindGroup bg = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetBufferViews(0, 1, &view)
-        .GetResult();
+    dawn::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}});
 
     // Use the buffer as both index and storage in the same pass
     dawn::CommandBufferBuilder builder = AssertWillBeError(device.CreateCommandBufferBuilder());
@@ -107,10 +104,7 @@
     dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout(device, {{
         0, dawn::ShaderStageBit::Vertex, dawn::BindingType::SampledTexture
     }});
-    dawn::BindGroup bg = device.CreateBindGroupBuilder()
-        .SetLayout(bgl)
-        .SetTextureViews(0, 1, &view)
-        .GetResult();
+    dawn::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}});
 
     // Create the render pass that will use the texture as an output attachment
     dawn::RenderPassDescriptor renderPass = device.CreateRenderPassDescriptorBuilder()
diff --git a/src/utils/DawnHelpers.cpp b/src/utils/DawnHelpers.cpp
index 5b8e0c9..9d2c15d 100644
--- a/src/utils/DawnHelpers.cpp
+++ b/src/utils/DawnHelpers.cpp
@@ -225,4 +225,47 @@
         return device.CreateBindGroupLayout(&descriptor);
     }
 
+    BindingInitializationHelper::BindingInitializationHelper(uint32_t binding,
+                                                             const dawn::Sampler& sampler)
+        : binding(binding), sampler(sampler) {
+    }
+
+    BindingInitializationHelper::BindingInitializationHelper(uint32_t binding,
+                                                             const dawn::TextureView& textureView)
+        : binding(binding), textureView(textureView) {
+    }
+
+    BindingInitializationHelper::BindingInitializationHelper(uint32_t binding,
+                                                             const dawn::BufferView& bufferView)
+        : binding(binding), bufferView(bufferView) {
+    }
+
+    dawn::BindGroupBinding BindingInitializationHelper::GetAsBinding() const {
+        dawn::BindGroupBinding result;
+
+        result.binding = binding;
+        result.sampler = sampler;
+        result.textureView = textureView;
+        result.bufferView = bufferView;
+
+        return result;
+    }
+
+    dawn::BindGroup MakeBindGroup(
+        const dawn::Device& device,
+        const dawn::BindGroupLayout& layout,
+        std::initializer_list<BindingInitializationHelper> bindingsInitializer) {
+        std::vector<dawn::BindGroupBinding> bindings;
+        for (const BindingInitializationHelper& helper : bindingsInitializer) {
+            bindings.push_back(helper.GetAsBinding());
+        }
+
+        dawn::BindGroupDescriptor descriptor;
+        descriptor.layout = layout;
+        descriptor.numBindings = bindings.size();
+        descriptor.bindings = bindings.data();
+
+        return device.CreateBindGroup(&descriptor);
+    }
+
 }  // namespace utils
diff --git a/src/utils/DawnHelpers.h b/src/utils/DawnHelpers.h
index 6d85a9e..a955a51 100644
--- a/src/utils/DawnHelpers.h
+++ b/src/utils/DawnHelpers.h
@@ -65,4 +65,32 @@
         const dawn::Device& device,
         std::initializer_list<dawn::BindGroupLayoutBinding> bindingsInitializer);
 
+    // Helpers to make creating bind groups look nicer:
+    //
+    //   utils::MakeBindGroup(device, layout, {
+    //       {0, mySampler},
+    //       {1, myBufferView},
+    //       {3, myTexture}
+    //   });
+
+    // Structure with one constructor per-type of bindings, so that the initializer_list accepts
+    // bindings with the right type and no extra information.
+    struct BindingInitializationHelper {
+        BindingInitializationHelper(uint32_t binding, const dawn::Sampler& sampler);
+        BindingInitializationHelper(uint32_t binding, const dawn::TextureView& textureView);
+        BindingInitializationHelper(uint32_t binding, const dawn::BufferView& bufferView);
+
+        dawn::BindGroupBinding GetAsBinding() const;
+
+        uint32_t binding;
+        dawn::Sampler sampler;
+        dawn::TextureView textureView;
+        dawn::BufferView bufferView;
+    };
+
+    dawn::BindGroup MakeBindGroup(
+        const dawn::Device& device,
+        const dawn::BindGroupLayout& layout,
+        std::initializer_list<BindingInitializationHelper> bindingsInitializer);
+
 }  // namespace utils