Implement getBindGroupLayout

This patch makes the |layout| member of the Render|ComputePipelineDescriptor
optional. If it is not provided, a default layout is created from the
ShaderModules provided and used to replace the layout in the descriptor.

Then, pipeline.GetBindGroupLayout may be called to get the existing, or
the computed bind group layout. If no bind group layout exists at the
provided index, an empty bind group layout is returned.

Bug: dawn:276
Change-Id: I276ed0296a2f1f2d8131fa906a4aefe85d75b3a7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/13741
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/BUILD.gn b/BUILD.gn
index 0fd9cf4..58e1278 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -789,6 +789,7 @@
     "src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp",
     "src/tests/unittests/validation/ErrorScopeValidationTests.cpp",
     "src/tests/unittests/validation/FenceValidationTests.cpp",
+    "src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp",
     "src/tests/unittests/validation/QueueSubmitValidationTests.cpp",
     "src/tests/unittests/validation/RenderBundleValidationTests.cpp",
     "src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp",
diff --git a/dawn.json b/dawn.json
index af922ec..44fce28 100644
--- a/dawn.json
+++ b/dawn.json
@@ -437,14 +437,23 @@
         ]
     },
     "compute pipeline": {
-        "category": "object"
+        "category": "object",
+        "methods": [
+            {
+                "name": "get bind group layout",
+                "returns": "bind group layout",
+                "args": [
+                    {"name": "group", "type": "uint32_t"}
+                ]
+            }
+        ]
     },
     "compute pipeline descriptor": {
         "category": "structure",
         "extensible": true,
         "members": [
             {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true},
-            {"name": "layout", "type": "pipeline layout"},
+            {"name": "layout", "type": "pipeline layout", "optional": true},
             {"name": "compute stage", "type": "programmable stage descriptor"}
         ]
     },
@@ -1118,14 +1127,23 @@
         ]
     },
     "render pipeline": {
-        "category": "object"
+        "category": "object",
+        "methods": [
+            {
+                "name": "get bind group layout",
+                "returns": "bind group layout",
+                "args": [
+                    {"name": "group", "type": "uint32_t"}
+                ]
+            }
+        ]
     },
     "render pipeline descriptor": {
         "category": "structure",
         "extensible": true,
         "members": [
             {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true},
-            {"name": "layout", "type": "pipeline layout"},
+            {"name": "layout", "type": "pipeline layout", "optional": true},
             {"name": "vertex stage", "type": "programmable stage descriptor"},
             {"name": "fragment stage", "type": "programmable stage descriptor", "annotation": "const*", "optional": true},
             {"name": "vertex state", "type": "vertex state descriptor", "annotation": "const*", "optional": true},
diff --git a/generator/templates/webgpu_cpp.h b/generator/templates/webgpu_cpp.h
index 911a16f..b110f56 100644
--- a/generator/templates/webgpu_cpp.h
+++ b/generator/templates/webgpu_cpp.h
@@ -108,6 +108,13 @@
             return static_cast<Derived&>(*this);
         }
 
+        bool operator==(std::nullptr_t) const {
+            return mHandle == nullptr;
+        }
+        bool operator!=(std::nullptr_t) const {
+            return mHandle != nullptr;
+        }
+
         explicit operator bool() const {
             return mHandle != nullptr;
         }
diff --git a/src/dawn_native/ComputePipeline.cpp b/src/dawn_native/ComputePipeline.cpp
index 137d648..c1394c6 100644
--- a/src/dawn_native/ComputePipeline.cpp
+++ b/src/dawn_native/ComputePipeline.cpp
@@ -25,7 +25,10 @@
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
-        DAWN_TRY(device->ValidateObject(descriptor->layout));
+        if (descriptor->layout != nullptr) {
+            DAWN_TRY(device->ValidateObject(descriptor->layout));
+        }
+
         DAWN_TRY(ValidateProgrammableStageDescriptor(
             device, &descriptor->computeStage, descriptor->layout, SingleShaderStage::Compute));
         return {};
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index cade900..664b81d 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -670,7 +670,21 @@
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateComputePipelineDescriptor(this, descriptor));
         }
-        DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(descriptor));
+
+        if (descriptor->layout == nullptr) {
+            ComputePipelineDescriptor descriptorWithDefaultLayout = *descriptor;
+
+            DAWN_TRY_ASSIGN(
+                descriptorWithDefaultLayout.layout,
+                PipelineLayoutBase::CreateDefault(this, &descriptor->computeStage.module, 1));
+            // Ref will keep the pipeline layout alive until the end of the function where
+            // the pipeline will take another reference.
+            Ref<PipelineLayoutBase> layoutRef = AcquireRef(descriptorWithDefaultLayout.layout);
+
+            DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(&descriptorWithDefaultLayout));
+        } else {
+            DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(descriptor));
+        }
         return {};
     }
 
@@ -705,7 +719,30 @@
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateRenderPipelineDescriptor(this, descriptor));
         }
-        DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(descriptor));
+
+        if (descriptor->layout == nullptr) {
+            RenderPipelineDescriptor descriptorWithDefaultLayout = *descriptor;
+
+            const ShaderModuleBase* modules[2];
+            modules[0] = descriptor->vertexStage.module;
+            uint32_t count;
+            if (descriptor->fragmentStage == nullptr) {
+                count = 1;
+            } else {
+                modules[1] = descriptor->fragmentStage->module;
+                count = 2;
+            }
+
+            DAWN_TRY_ASSIGN(descriptorWithDefaultLayout.layout,
+                            PipelineLayoutBase::CreateDefault(this, modules, count));
+            // Ref will keep the pipeline layout alive until the end of the function where
+            // the pipeline will take another reference.
+            Ref<PipelineLayoutBase> layoutRef = AcquireRef(descriptorWithDefaultLayout.layout);
+
+            DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(&descriptorWithDefaultLayout));
+        } else {
+            DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(descriptor));
+        }
         return {};
     }
 
diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp
index 79623e1..c553784 100644
--- a/src/dawn_native/Format.cpp
+++ b/src/dawn_native/Format.cpp
@@ -22,8 +22,17 @@
 
     // Format
 
+    // static
     Format::Type Format::TextureComponentTypeToFormatType(
         wgpu::TextureComponentType componentType) {
+        switch (componentType) {
+            case wgpu::TextureComponentType::Float:
+            case wgpu::TextureComponentType::Sint:
+            case wgpu::TextureComponentType::Uint:
+                break;
+            default:
+                UNREACHABLE();
+        }
         // Check that Type correctly mirrors TextureComponentType except for "Other".
         static_assert(static_cast<Type>(wgpu::TextureComponentType::Float) == Type::Float, "");
         static_assert(static_cast<Type>(wgpu::TextureComponentType::Sint) == Type::Sint, "");
@@ -31,6 +40,23 @@
         return static_cast<Type>(componentType);
     }
 
+    // static
+    wgpu::TextureComponentType Format::FormatTypeToTextureComponentType(Type type) {
+        switch (type) {
+            case Type::Float:
+            case Type::Sint:
+            case Type::Uint:
+                break;
+            default:
+                UNREACHABLE();
+        }
+        // Check that Type correctly mirrors TextureComponentType except for "Other".
+        static_assert(static_cast<Type>(wgpu::TextureComponentType::Float) == Type::Float, "");
+        static_assert(static_cast<Type>(wgpu::TextureComponentType::Sint) == Type::Sint, "");
+        static_assert(static_cast<Type>(wgpu::TextureComponentType::Uint) == Type::Uint, "");
+        return static_cast<wgpu::TextureComponentType>(type);
+    }
+
     bool Format::IsColor() const {
         return aspect == Aspect::Color;
     }
diff --git a/src/dawn_native/Format.h b/src/dawn_native/Format.h
index 9b5cfbe..f1e4fbb 100644
--- a/src/dawn_native/Format.h
+++ b/src/dawn_native/Format.h
@@ -58,6 +58,7 @@
         uint32_t blockHeight;
 
         static Type TextureComponentTypeToFormatType(wgpu::TextureComponentType componentType);
+        static wgpu::TextureComponentType FormatTypeToTextureComponentType(Type type);
 
         bool IsColor() const;
         bool HasDepth() const;
diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp
index 9d24011..ad819d1 100644
--- a/src/dawn_native/Pipeline.cpp
+++ b/src/dawn_native/Pipeline.cpp
@@ -14,6 +14,7 @@
 
 #include "dawn_native/Pipeline.h"
 
+#include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/ShaderModule.h"
@@ -32,7 +33,7 @@
         if (descriptor->module->GetExecutionModel() != stage) {
             return DAWN_VALIDATION_ERROR("Setting module with wrong stages");
         }
-        if (!descriptor->module->IsCompatibleWithPipelineLayout(layout)) {
+        if (layout != nullptr && !descriptor->module->IsCompatibleWithPipelineLayout(layout)) {
             return DAWN_VALIDATION_ERROR("Stage not compatible with layout");
         }
         return {};
@@ -65,4 +66,41 @@
         return mLayout.Get();
     }
 
+    MaybeError PipelineBase::ValidateGetBindGroupLayout(uint32_t group) {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+        DAWN_TRY(GetDevice()->ValidateObject(mLayout.Get()));
+        if (group >= kMaxBindGroups) {
+            return DAWN_VALIDATION_ERROR("Bind group layout index out of bounds");
+        }
+        return {};
+    }
+
+    BindGroupLayoutBase* PipelineBase::GetBindGroupLayout(uint32_t group) {
+        if (GetDevice()->ConsumedError(ValidateGetBindGroupLayout(group))) {
+            return BindGroupLayoutBase::MakeError(GetDevice());
+        }
+
+        if (!mLayout->GetBindGroupLayoutsMask()[group]) {
+            // Get or create an empty bind group layout.
+            // TODO(enga): Consider caching this object on the Device and reusing it.
+            // Today, this can't be done correctly because of the order of Device destruction.
+            // For example, vulkan::~Device will be called before ~DeviceBase. If DeviceBase owns
+            // a Ref<BindGroupLayoutBase>, then the VkDevice will be destroyed before the
+            // VkDescriptorSetLayout.
+            BindGroupLayoutDescriptor desc = {};
+            desc.bindingCount = 0;
+            desc.bindings = nullptr;
+
+            BindGroupLayoutBase* bgl = nullptr;
+            if (GetDevice()->ConsumedError(GetDevice()->GetOrCreateBindGroupLayout(&desc), &bgl)) {
+                return BindGroupLayoutBase::MakeError(GetDevice());
+            }
+            return bgl;
+        }
+
+        BindGroupLayoutBase* bgl = mLayout->GetBindGroupLayout(group);
+        bgl->Reference();
+        return bgl;
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h
index 5c6e6ee..29c8386 100644
--- a/src/dawn_native/Pipeline.h
+++ b/src/dawn_native/Pipeline.h
@@ -38,12 +38,15 @@
         wgpu::ShaderStage GetStageMask() const;
         PipelineLayoutBase* GetLayout();
         const PipelineLayoutBase* GetLayout() const;
+        BindGroupLayoutBase* GetBindGroupLayout(uint32_t group);
 
       protected:
         PipelineBase(DeviceBase* device, PipelineLayoutBase* layout, wgpu::ShaderStage stages);
         PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
 
       private:
+        MaybeError ValidateGetBindGroupLayout(uint32_t group);
+
         wgpu::ShaderStage mStageMask;
         Ref<PipelineLayoutBase> mLayout;
     };
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 4aea64b..b76e553 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -19,9 +19,22 @@
 #include "common/HashUtils.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/ShaderModule.h"
 
 namespace dawn_native {
 
+    namespace {
+
+        bool operator==(const BindGroupLayoutBinding& lhs, const BindGroupLayoutBinding& rhs) {
+            return lhs.binding == rhs.binding && lhs.visibility == rhs.visibility &&
+                   lhs.type == rhs.type && lhs.hasDynamicOffset == rhs.hasDynamicOffset &&
+                   lhs.multisampled == rhs.multisampled &&
+                   lhs.textureDimension == rhs.textureDimension &&
+                   lhs.textureComponentType == rhs.textureComponentType;
+        }
+
+    }  // anonymous namespace
+
     MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
                                                 const PipelineLayoutDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
@@ -81,11 +94,126 @@
         return new PipelineLayoutBase(device, ObjectBase::kError);
     }
 
-    const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(size_t group) const {
+    // static
+    ResultOrError<PipelineLayoutBase*> PipelineLayoutBase::CreateDefault(
+        DeviceBase* device,
+        const ShaderModuleBase* const* modules,
+        uint32_t count) {
+        ASSERT(count > 0);
+
+        // Data which BindGroupLayoutDescriptor will point to for creation
+        std::array<std::array<BindGroupLayoutBinding, kMaxBindingsPerGroup>, kMaxBindGroups>
+            bindingData = {};
+
+        // Bitsets of used bindings
+        std::array<std::bitset<kMaxBindingsPerGroup>, kMaxBindGroups> usedBindings = {};
+
+        // A flat map of bindings to the index in |bindingData|
+        std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups> usedBindingsMap = {};
+
+        // A counter of how many bindings we've populated in |bindingData|
+        std::array<uint32_t, kMaxBindGroups> bindingCounts = {};
+
+        uint32_t bindGroupLayoutCount = 0;
+        for (uint32_t moduleIndex = 0; moduleIndex < count; ++moduleIndex) {
+            const ShaderModuleBase* module = modules[moduleIndex];
+            const ShaderModuleBase::ModuleBindingInfo& info = module->GetBindingInfo();
+
+            for (uint32_t group = 0; group < info.size(); ++group) {
+                for (uint32_t binding = 0; binding < info[group].size(); ++binding) {
+                    const ShaderModuleBase::BindingInfo& bindingInfo = info[group][binding];
+                    if (!bindingInfo.used) {
+                        continue;
+                    }
+
+                    if (bindingInfo.multisampled) {
+                        return DAWN_VALIDATION_ERROR("Multisampled textures not supported (yet)");
+                    }
+
+                    BindGroupLayoutBinding bindingSlot;
+                    bindingSlot.binding = binding;
+                    bindingSlot.visibility = wgpu::ShaderStage::Vertex |
+                                             wgpu::ShaderStage::Fragment |
+                                             wgpu::ShaderStage::Compute;
+                    bindingSlot.type = bindingInfo.type;
+                    bindingSlot.hasDynamicOffset = false;
+                    bindingSlot.multisampled = bindingInfo.multisampled;
+                    bindingSlot.textureDimension = bindingInfo.textureDimension;
+                    bindingSlot.textureComponentType =
+                        Format::FormatTypeToTextureComponentType(bindingInfo.textureComponentType);
+
+                    if (usedBindings[group][binding]) {
+                        if (bindingSlot == bindingData[group][usedBindingsMap[group][binding]]) {
+                            // Already used and the data is the same. Continue.
+                            continue;
+                        } else {
+                            return DAWN_VALIDATION_ERROR(
+                                "Duplicate binding in default pipeline layout initialization not "
+                                "compatible with previous declaration");
+                        }
+                    }
+
+                    uint32_t currentBindingCount = bindingCounts[group];
+                    bindingData[group][currentBindingCount] = bindingSlot;
+
+                    usedBindingsMap[group][binding] = currentBindingCount;
+                    usedBindings[group].set(binding);
+
+                    bindingCounts[group]++;
+
+                    bindGroupLayoutCount = std::max(bindGroupLayoutCount, group + 1);
+                }
+            }
+        }
+
+        std::array<BindGroupLayoutBase*, kMaxBindGroups> bindGroupLayouts = {};
+        for (uint32_t group = 0; group < bindGroupLayoutCount; ++group) {
+            BindGroupLayoutDescriptor desc = {};
+            desc.bindings = bindingData[group].data();
+            desc.bindingCount = bindingCounts[group];
+
+            // We should never produce a bad descriptor.
+            ASSERT(!ValidateBindGroupLayoutDescriptor(device, &desc).IsError());
+            DAWN_TRY_ASSIGN(bindGroupLayouts[group], device->GetOrCreateBindGroupLayout(&desc));
+        }
+
+        PipelineLayoutDescriptor desc = {};
+        desc.bindGroupLayouts = bindGroupLayouts.data();
+        desc.bindGroupLayoutCount = bindGroupLayoutCount;
+        PipelineLayoutBase* pipelineLayout = device->CreatePipelineLayout(&desc);
+        ASSERT(!pipelineLayout->IsError());
+
+        // These bind group layouts are created internally and referenced by the pipeline layout.
+        // Release the external refcount.
+        for (uint32_t group = 0; group < bindGroupLayoutCount; ++group) {
+            if (bindGroupLayouts[group] != nullptr) {
+                bindGroupLayouts[group]->Release();
+            }
+        }
+
+        for (uint32_t moduleIndex = 0; moduleIndex < count; ++moduleIndex) {
+            ASSERT(modules[moduleIndex]->IsCompatibleWithPipelineLayout(pipelineLayout));
+        }
+
+        return pipelineLayout;
+    }
+
+    const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(uint32_t group) const {
         ASSERT(!IsError());
         ASSERT(group < kMaxBindGroups);
         ASSERT(mMask[group]);
-        return mBindGroupLayouts[group].Get();
+        const BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get();
+        ASSERT(bgl != nullptr);
+        return bgl;
+    }
+
+    BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(uint32_t group) {
+        ASSERT(!IsError());
+        ASSERT(group < kMaxBindGroups);
+        ASSERT(mMask[group]);
+        BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get();
+        ASSERT(bgl != nullptr);
+        return bgl;
     }
 
     const std::bitset<kMaxBindGroups> PipelineLayoutBase::GetBindGroupLayoutsMask() const {
diff --git a/src/dawn_native/PipelineLayout.h b/src/dawn_native/PipelineLayout.h
index fd769f2..f919eff 100644
--- a/src/dawn_native/PipelineLayout.h
+++ b/src/dawn_native/PipelineLayout.h
@@ -38,8 +38,11 @@
         ~PipelineLayoutBase() override;
 
         static PipelineLayoutBase* MakeError(DeviceBase* device);
+        static ResultOrError<PipelineLayoutBase*>
+        CreateDefault(DeviceBase* device, const ShaderModuleBase* const* modules, uint32_t count);
 
-        const BindGroupLayoutBase* GetBindGroupLayout(size_t group) const;
+        const BindGroupLayoutBase* GetBindGroupLayout(uint32_t group) const;
+        BindGroupLayoutBase* GetBindGroupLayout(uint32_t group);
         const std::bitset<kMaxBindGroups> GetBindGroupLayoutsMask() const;
 
         // Utility functions to compute inherited bind groups.
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index 160b3d2..be87040 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -280,7 +280,9 @@
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
-        DAWN_TRY(device->ValidateObject(descriptor->layout));
+        if (descriptor->layout != nullptr) {
+            DAWN_TRY(device->ValidateObject(descriptor->layout));
+        }
 
         // TODO(crbug.com/dawn/136): Support vertex-only pipelines.
         if (descriptor->fragmentStage == nullptr) {
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index 2879b51..dfa9acf 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -175,36 +175,37 @@
                     continue;
                 }
 
-                auto& info = mBindingInfo[set][binding];
-                info.used = true;
-                info.id = resource.id;
-                info.base_type_id = resource.base_type_id;
+                BindingInfo* info = &mBindingInfo[set][binding];
+                *info = {};
+                info->used = true;
+                info->id = resource.id;
+                info->base_type_id = resource.base_type_id;
                 switch (bindingType) {
                     case wgpu::BindingType::SampledTexture: {
                         spirv_cross::SPIRType::ImageType imageType =
-                            compiler.get_type(info.base_type_id).image;
+                            compiler.get_type(info->base_type_id).image;
                         spirv_cross::SPIRType::BaseType textureComponentType =
                             compiler.get_type(imageType.type).basetype;
 
-                        info.multisampled = imageType.ms;
-                        info.textureDimension =
+                        info->multisampled = imageType.ms;
+                        info->textureDimension =
                             SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
-                        info.textureComponentType =
+                        info->textureComponentType =
                             SpirvCrossBaseTypeToFormatType(textureComponentType);
-                        info.type = bindingType;
+                        info->type = bindingType;
                     } break;
                     case wgpu::BindingType::StorageBuffer: {
                         // Differentiate between readonly storage bindings and writable ones based
                         // on the NonWritable decoration
                         spirv_cross::Bitset flags = compiler.get_buffer_block_flags(resource.id);
                         if (flags.get(spv::DecorationNonWritable)) {
-                            info.type = wgpu::BindingType::ReadonlyStorageBuffer;
+                            info->type = wgpu::BindingType::ReadonlyStorageBuffer;
                         } else {
-                            info.type = wgpu::BindingType::StorageBuffer;
+                            info->type = wgpu::BindingType::StorageBuffer;
                         }
                     } break;
                     default:
-                        info.type = bindingType;
+                        info->type = bindingType;
                 }
             }
         };
@@ -296,7 +297,7 @@
         return mExecutionModel;
     }
 
-    bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) {
+    bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) const {
         ASSERT(!IsError());
 
         for (uint32_t group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
@@ -316,8 +317,9 @@
         return true;
     }
 
-    bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(size_t group,
-                                                           const BindGroupLayoutBase* layout) {
+    bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(
+        size_t group,
+        const BindGroupLayoutBase* layout) const {
         ASSERT(!IsError());
 
         const auto& layoutInfo = layout->GetBindingInfo();
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index dedde2f..42756be 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -69,7 +69,7 @@
         using FragmentOutputBaseTypes = std::array<Format::Type, kMaxColorAttachments>;
         const FragmentOutputBaseTypes& GetFragmentOutputBaseTypes() const;
 
-        bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout);
+        bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) const;
 
         // Functors necessary for the unordered_set<ShaderModuleBase*>-based cache.
         struct HashFunc {
@@ -82,7 +82,7 @@
       private:
         ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
 
-        bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout);
+        bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout) const;
 
         // TODO(cwallez@chromium.org): The code is only stored for deduplication. We could maybe
         // store a cryptographic hash of the code instead?
diff --git a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
new file mode 100644
index 0000000..18673ea
--- /dev/null
+++ b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -0,0 +1,674 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/unittests/validation/ValidationTest.h"
+
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+class GetBindGroupLayoutTests : public ValidationTest {
+  protected:
+    static constexpr wgpu::ShaderStage kVisibilityAll =
+        wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex;
+
+    wgpu::RenderPipeline RenderPipelineFromVertexShader(const char* shader) {
+        wgpu::ShaderModule vsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, shader);
+        wgpu::ShaderModule fsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        void main() {
+        })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = vsModule;
+        descriptor.cFragmentStage.module = fsModule;
+
+        return device.CreateRenderPipeline(&descriptor);
+    }
+};
+
+// Test that GetBindGroupLayout returns the same object for the same index
+// and for matching layouts.
+TEST_F(GetBindGroupLayoutTests, SameObject) {
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform UniformBuffer1 {
+            vec4 pos1;
+        };
+
+        layout(set = 1, binding = 0) uniform UniformBuffer2 {
+            vec4 pos2;
+        };
+
+        void main() {
+        })");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 2, binding = 0) uniform UniformBuffer3 {
+            vec4 pos3;
+        };
+
+        layout(set = 3, binding = 0) buffer StorageBuffer {
+            mat4 pos4;
+        };
+
+        void main() {
+        })");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
+
+    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(0).Get());
+
+    EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), pipeline.GetBindGroupLayout(1).Get());
+
+    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(1).Get());
+
+    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(2).Get());
+
+    EXPECT_NE(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(3).Get());
+}
+
+// Test that getBindGroupLayout defaults are correct
+// - shader stage visibility is All
+// - dynamic offsets is false
+TEST_F(GetBindGroupLayoutTests, DefaultShaderStageAndDynamicOffsets) {
+    wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform UniformBuffer {
+            vec4 pos;
+        };
+
+        void main() {
+        })");
+
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::UniformBuffer;
+    binding.multisampled = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    // Check that visibility and dynamic offsets match
+    binding.hasDynamicOffset = false;
+    binding.visibility = kVisibilityAll;
+    EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+
+    // Check that any change in visibility doesn't match.
+    binding.visibility = wgpu::ShaderStage::Vertex;
+    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+
+    binding.visibility = wgpu::ShaderStage::Fragment;
+    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+
+    binding.visibility = wgpu::ShaderStage::Compute;
+    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+
+    // Check that any change in hasDynamicOffsets doesn't match.
+    binding.hasDynamicOffset = true;
+    binding.visibility = kVisibilityAll;
+    EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+}
+
+// Test GetBindGroupLayout works with a compute pipeline
+TEST_F(GetBindGroupLayoutTests, ComputePipeline) {
+    wgpu::ShaderModule csModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform UniformBuffer {
+            vec4 pos;
+        };
+        void main() {
+        })");
+
+    wgpu::ComputePipelineDescriptor descriptor;
+    descriptor.layout = nullptr;
+    descriptor.computeStage.module = csModule;
+    descriptor.computeStage.entryPoint = "main";
+
+    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor);
+
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::UniformBuffer;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+}
+
+// Test that the binding type matches the shader.
+TEST_F(GetBindGroupLayoutTests, BindingType) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+    binding.multisampled = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    {
+        binding.type = wgpu::BindingType::UniformBuffer;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform Buffer {
+            vec4 pos;
+        };
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.type = wgpu::BindingType::StorageBuffer;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) buffer Storage {
+            vec4 pos;
+        };
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.type = wgpu::BindingType::ReadonlyStorageBuffer;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) readonly buffer Storage {
+            vec4 pos;
+        };
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.type = wgpu::BindingType::SampledTexture;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.type = wgpu::BindingType::Sampler;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform sampler samp;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+}
+
+// Test that multisampling matches the shader.
+TEST_F(GetBindGroupLayoutTests, Multisampled) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::SampledTexture;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    {
+        binding.multisampled = false;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    // TODO: Support multisampling
+    GTEST_SKIP() << "Multisampling unimplemented";
+    {
+        binding.multisampled = true;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2DMS tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+}
+
+// Test that texture view dimension matches the shader.
+TEST_F(GetBindGroupLayoutTests, TextureDimension) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::SampledTexture;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+    binding.multisampled = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::e1D;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture1D tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::e2D;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::e2DArray;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2DArray tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::e3D;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture3D tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::Cube;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform textureCube tex;
+
+        void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureDimension = wgpu::TextureViewDimension::CubeArray;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform textureCubeArray tex;
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+}
+
+// Test that texture component type matches the shader.
+TEST_F(GetBindGroupLayoutTests, TextureComponentType) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::SampledTexture;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+    binding.multisampled = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    {
+        binding.textureComponentType = wgpu::TextureComponentType::Float;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform texture2D tex;
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureComponentType = wgpu::TextureComponentType::Sint;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform itexture2D tex;
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.textureComponentType = wgpu::TextureComponentType::Uint;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform utexture2D tex;
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+}
+
+// Test that binding= indices match.
+TEST_F(GetBindGroupLayoutTests, BindingIndices) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.type = wgpu::BindingType::UniformBuffer;
+    binding.visibility = kVisibilityAll;
+    binding.hasDynamicOffset = false;
+    binding.multisampled = false;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 1;
+    desc.bindings = &binding;
+
+    {
+        binding.binding = 0;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform Buffer {
+                    vec4 pos;
+                };
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.binding = 1;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 1) uniform Buffer {
+                    vec4 pos;
+                };
+
+                void main() {})");
+        EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+
+    {
+        binding.binding = 2;
+        wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+                #version 450
+                layout(set = 0, binding = 1) uniform Buffer {
+                    vec4 pos;
+                };
+
+                void main() {})");
+        EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
+    }
+}
+
+// Test it is valid to have duplicate bindings in the shaders.
+TEST_F(GetBindGroupLayoutTests, DuplicateBinding) {
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform UniformBuffer1 {
+            vec4 pos1;
+        };
+
+        layout(set = 1, binding = 0) uniform UniformBuffer2 {
+            vec4 pos2;
+        };
+
+        void main() {})");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 1, binding = 0) uniform UniformBuffer3 {
+            vec4 pos3;
+        };
+
+        void main() {})");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    device.CreateRenderPipeline(&descriptor);
+}
+
+// Test it is invalid to have conflicting binding types in the shaders.
+TEST_F(GetBindGroupLayoutTests, ConflictingBindingType) {
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform UniformBuffer {
+            vec4 pos;
+        };
+
+        void main() {})");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 0, binding = 0) buffer StorageBuffer {
+            vec4 pos;
+        };
+
+        void main() {})");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test it is invalid to have conflicting binding texture multisampling in the shaders.
+TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureMultisampling) {
+    // TODO: Support multisampling
+    GTEST_SKIP() << "Multisampling unimplemented";
+
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2DMS tex;
+
+        void main() {})");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test it is invalid to have conflicting binding texture dimension in the shaders.
+TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureDimension) {
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture3D tex;
+
+        void main() {})");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test it is invalid to have conflicting binding texture component type in the shaders.
+TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureComponentType) {
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform texture2D tex;
+
+        void main() {})");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform utexture2D tex;
+
+        void main() {})");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.layout = nullptr;
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = fsModule;
+
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test it is an error to query an out of range bind group layout.
+TEST_F(GetBindGroupLayoutTests, OutOfRangeIndex) {
+    ASSERT_DEVICE_ERROR(RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform Buffer1 {
+            vec4 pos1;
+        };
+        void main() {})")
+                            .GetBindGroupLayout(kMaxBindGroups));
+
+    ASSERT_DEVICE_ERROR(RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform Buffer1 {
+            vec4 pos1;
+        };
+        void main() {})")
+                            .GetBindGroupLayout(kMaxBindGroups + 1));
+}
+
+// Test that unused indices return the empty bind group layout.
+TEST_F(GetBindGroupLayoutTests, UnusedIndex) {
+    wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform Buffer1 {
+            vec4 pos1;
+        };
+
+        layout(set = 2, binding = 0) uniform Buffer2 {
+            vec4 pos2;
+        };
+
+        void main() {})");
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.bindingCount = 0;
+    desc.bindings = nullptr;
+
+    wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&desc);
+
+    EXPECT_NE(pipeline.GetBindGroupLayout(0).Get(), emptyBindGroupLayout.Get());  // Used
+    EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), emptyBindGroupLayout.Get());  // Not Used.
+    EXPECT_NE(pipeline.GetBindGroupLayout(2).Get(), emptyBindGroupLayout.Get());  // Used.
+    EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());  // Not used
+}
+
+// Test that after explicitly creating a pipeline with a pipeline layout, calling
+// GetBindGroupLayout reflects the same bind group layouts.
+TEST_F(GetBindGroupLayoutTests, Reflection) {
+    wgpu::BindGroupLayoutBinding binding = {};
+    binding.binding = 0;
+    binding.type = wgpu::BindingType::UniformBuffer;
+    binding.visibility = wgpu::ShaderStage::Vertex;
+
+    wgpu::BindGroupLayoutDescriptor bglDesc = {};
+    bglDesc.bindingCount = 1;
+    bglDesc.bindings = &binding;
+
+    wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(&bglDesc);
+
+    wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = {};
+    pipelineLayoutDesc.bindGroupLayoutCount = 1;
+    pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout;
+
+    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc);
+
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        layout(set = 0, binding = 0) uniform Buffer1 {
+            vec4 pos1;
+        };
+
+        void main() {
+        })");
+
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        void main() {
+        })");
+
+    utils::ComboRenderPipelineDescriptor pipelineDesc(device);
+    pipelineDesc.layout = pipelineLayout;
+    pipelineDesc.vertexStage.module = vsModule;
+    pipelineDesc.cFragmentStage.module = fsModule;
+
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
+
+    EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), bindGroupLayout.Get());
+
+    {
+        wgpu::BindGroupLayoutDescriptor emptyDesc = {};
+        emptyDesc.bindingCount = 0;
+        emptyDesc.bindings = nullptr;
+
+        wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&emptyDesc);
+
+        // Check that the rest of the bind group layouts reflect the empty one.
+        EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), emptyBindGroupLayout.Get());
+        EXPECT_EQ(pipeline.GetBindGroupLayout(2).Get(), emptyBindGroupLayout.Get());
+        EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());
+    }
+}