Raise maxStorageTexturesPerShaderStage to 8

The higher tier for this limit is available on all D3D12, all Metal,
and most Vulkan devices.

Bug: dawn:685
Change-Id: Ic2a39ad7908ea178e7aac48b7bb54b262d7039cf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/121543
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Shrek Shao <shrekshao@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/common/Constants.h b/src/dawn/common/Constants.h
index ca4c3b6..3604b5c 100644
--- a/src/dawn/common/Constants.h
+++ b/src/dawn/common/Constants.h
@@ -31,11 +31,11 @@
 static constexpr uint64_t kAssumedMaxBufferSize =
     0x80000000u;  // Use 2 GB when the limit is unavailable
 
-// Per stage limits
+// Per stage maximum limits used to optimized Dawn internals.
 static constexpr uint32_t kMaxSampledTexturesPerShaderStage = 16;
 static constexpr uint32_t kMaxSamplersPerShaderStage = 16;
 static constexpr uint32_t kMaxStorageBuffersPerShaderStage = 8;
-static constexpr uint32_t kMaxStorageTexturesPerShaderStage = 4;
+static constexpr uint32_t kMaxStorageTexturesPerShaderStage = 8;
 static constexpr uint32_t kMaxUniformBuffersPerShaderStage = 12;
 
 // Indirect command sizes
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index ba96937..24ce3f5 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -112,80 +112,85 @@
         limits.v1.maxDynamicStorageBuffersPerPipelineLayout);
 
     for (SingleShaderStage stage : IterateStages(kAllStages)) {
-        DAWN_INVALID_IF(
-            bindingCounts.perStage[stage].sampledTextureCount > kMaxSampledTexturesPerShaderStage,
-            "The number of sampled textures (%u) in the %s stage exceeds the maximum "
-            "per-stage limit (%u).",
-            bindingCounts.perStage[stage].sampledTextureCount, stage,
-            kMaxSampledTexturesPerShaderStage);
+        DAWN_INVALID_IF(bindingCounts.perStage[stage].sampledTextureCount >
+                            limits.v1.maxSampledTexturesPerShaderStage,
+                        "The number of sampled textures (%u) in the %s stage exceeds the maximum "
+                        "per-stage limit (%u).",
+                        bindingCounts.perStage[stage].sampledTextureCount, stage,
+                        limits.v1.maxSampledTexturesPerShaderStage);
 
         // The per-stage number of external textures is bound by the maximum sampled textures
         // per stage.
-        DAWN_INVALID_IF(bindingCounts.perStage[stage].externalTextureCount >
-                            kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
-                        "The number of external textures (%u) in the %s stage exceeds the maximum "
-                        "per-stage limit (%u).",
-                        bindingCounts.perStage[stage].externalTextureCount, stage,
-                        kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture);
+        DAWN_INVALID_IF(
+            bindingCounts.perStage[stage].externalTextureCount >
+                limits.v1.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
+            "The number of external textures (%u) in the %s stage exceeds the maximum "
+            "per-stage limit (%u).",
+            bindingCounts.perStage[stage].externalTextureCount, stage,
+            limits.v1.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture);
 
         DAWN_INVALID_IF(
             bindingCounts.perStage[stage].sampledTextureCount +
                     (bindingCounts.perStage[stage].externalTextureCount *
                      kSampledTexturesPerExternalTexture) >
-                kMaxSampledTexturesPerShaderStage,
+                limits.v1.maxSampledTexturesPerShaderStage,
             "The combination of sampled textures (%u) and external textures (%u) in the %s "
             "stage exceeds the maximum per-stage limit (%u).",
             bindingCounts.perStage[stage].sampledTextureCount,
             bindingCounts.perStage[stage].externalTextureCount, stage,
-            kMaxSampledTexturesPerShaderStage);
+            limits.v1.maxSampledTexturesPerShaderStage);
 
         DAWN_INVALID_IF(
-            bindingCounts.perStage[stage].samplerCount > kMaxSamplersPerShaderStage,
+            bindingCounts.perStage[stage].samplerCount > limits.v1.maxSamplersPerShaderStage,
             "The number of samplers (%u) in the %s stage exceeds the maximum per-stage limit "
             "(%u).",
-            bindingCounts.perStage[stage].samplerCount, stage, kMaxSamplersPerShaderStage);
+            bindingCounts.perStage[stage].samplerCount, stage, limits.v1.maxSamplersPerShaderStage);
 
         DAWN_INVALID_IF(
             bindingCounts.perStage[stage].samplerCount +
                     (bindingCounts.perStage[stage].externalTextureCount *
                      kSamplersPerExternalTexture) >
-                kMaxSamplersPerShaderStage,
+                limits.v1.maxSamplersPerShaderStage,
             "The combination of samplers (%u) and external textures (%u) in the %s stage "
             "exceeds the maximum per-stage limit (%u).",
             bindingCounts.perStage[stage].samplerCount,
-            bindingCounts.perStage[stage].externalTextureCount, stage, kMaxSamplersPerShaderStage);
+            bindingCounts.perStage[stage].externalTextureCount, stage,
+            limits.v1.maxSamplersPerShaderStage);
 
         DAWN_INVALID_IF(
-            bindingCounts.perStage[stage].storageBufferCount > kMaxStorageBuffersPerShaderStage,
+            bindingCounts.perStage[stage].storageBufferCount >
+                limits.v1.maxStorageBuffersPerShaderStage,
             "The number of storage buffers (%u) in the %s stage exceeds the maximum per-stage "
             "limit (%u).",
             bindingCounts.perStage[stage].storageBufferCount, stage,
-            kMaxStorageBuffersPerShaderStage);
+            limits.v1.maxStorageBuffersPerShaderStage);
 
         DAWN_INVALID_IF(
-            bindingCounts.perStage[stage].storageTextureCount > kMaxStorageTexturesPerShaderStage,
+            bindingCounts.perStage[stage].storageTextureCount >
+                limits.v1.maxStorageTexturesPerShaderStage,
             "The number of storage textures (%u) in the %s stage exceeds the maximum per-stage "
             "limit (%u).",
             bindingCounts.perStage[stage].storageTextureCount, stage,
-            kMaxStorageTexturesPerShaderStage);
+            limits.v1.maxStorageTexturesPerShaderStage);
 
         DAWN_INVALID_IF(
-            bindingCounts.perStage[stage].uniformBufferCount > kMaxUniformBuffersPerShaderStage,
+            bindingCounts.perStage[stage].uniformBufferCount >
+                limits.v1.maxUniformBuffersPerShaderStage,
             "The number of uniform buffers (%u) in the %s stage exceeds the maximum per-stage "
             "limit (%u).",
             bindingCounts.perStage[stage].uniformBufferCount, stage,
-            kMaxUniformBuffersPerShaderStage);
+            limits.v1.maxUniformBuffersPerShaderStage);
 
         DAWN_INVALID_IF(
             bindingCounts.perStage[stage].uniformBufferCount +
                     (bindingCounts.perStage[stage].externalTextureCount *
                      kUniformsPerExternalTexture) >
-                kMaxUniformBuffersPerShaderStage,
+                limits.v1.maxUniformBuffersPerShaderStage,
             "The combination of uniform buffers (%u) and external textures (%u) in the %s "
             "stage exceeds the maximum per-stage limit (%u).",
             bindingCounts.perStage[stage].uniformBufferCount,
             bindingCounts.perStage[stage].externalTextureCount, stage,
-            kMaxUniformBuffersPerShaderStage);
+            limits.v1.maxUniformBuffersPerShaderStage);
     }
 
     return {};
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index a17c355..fb14fd2 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -38,6 +38,11 @@
 #define LIMITS_RESOURCE_BINDINGS(X)                                                \
     X(Maximum,   maxDynamicUniformBuffersPerPipelineLayout,         8,         10) \
     X(Maximum,   maxDynamicStorageBuffersPerPipelineLayout,         4,          8) \
+    X(Maximum,            maxSampledTexturesPerShaderStage,        16,         16) \
+    X(Maximum,                   maxSamplersPerShaderStage,        16,         16) \
+    X(Maximum,             maxStorageBuffersPerShaderStage,         8,          8) \
+    X(Maximum,            maxStorageTexturesPerShaderStage,         4,          8) \
+    X(Maximum,             maxUniformBuffersPerShaderStage,        12,         12)
 
 // TODO(crbug.com/dawn/685):
 // These limits don't have tiers yet. Define two tiers with the same values since the macros
@@ -49,11 +54,6 @@
     X(Maximum,                       maxTextureArrayLayers,       256,        256) \
     X(Maximum,                               maxBindGroups,         4,          4) \
     X(Maximum,                     maxBindingsPerBindGroup,       640,        640) \
-    X(Maximum,            maxSampledTexturesPerShaderStage,        16,         16) \
-    X(Maximum,                   maxSamplersPerShaderStage,        16,         16) \
-    X(Maximum,             maxStorageBuffersPerShaderStage,         8,          8) \
-    X(Maximum,            maxStorageTexturesPerShaderStage,         4,          4) \
-    X(Maximum,             maxUniformBuffersPerShaderStage,        12,         12) \
     X(Maximum,                 maxUniformBufferBindingSize,     65536,      65536) \
     X(Alignment,           minUniformBufferOffsetAlignment,       256,        256) \
     X(Alignment,           minStorageBufferOffsetAlignment,       256,        256) \
diff --git a/src/dawn/tests/end2end/BindGroupTests.cpp b/src/dawn/tests/end2end/BindGroupTests.cpp
index a7b6e0d..2117c2e 100644
--- a/src/dawn/tests/end2end/BindGroupTests.cpp
+++ b/src/dawn/tests/end2end/BindGroupTests.cpp
@@ -1427,150 +1427,6 @@
     EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderPass.color, 0, 0);
 }
 
-// Test that creating a large bind group, with each binding type at the max count, works and can be
-// used correctly. The test loads a different value from each binding, and writes 1 to a storage
-// buffer if all values are correct.
-TEST_P(BindGroupTests, ReallyLargeBindGroup) {
-    DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
-    std::ostringstream interface;
-    std::ostringstream body;
-    uint32_t binding = 0;
-    uint32_t expectedValue = 42;
-
-    wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
-
-    auto CreateTextureWithRedData = [&](wgpu::TextureFormat format, uint32_t value,
-                                        wgpu::TextureUsage usage) {
-        wgpu::TextureDescriptor textureDesc = {};
-        textureDesc.usage = wgpu::TextureUsage::CopyDst | usage;
-        textureDesc.size = {1, 1, 1};
-        textureDesc.format = format;
-        wgpu::Texture texture = device.CreateTexture(&textureDesc);
-
-        if (format == wgpu::TextureFormat::R8Unorm) {
-            ASSERT(expectedValue < 255u);
-        }
-        wgpu::Buffer textureData =
-            utils::CreateBufferFromData(device, wgpu::BufferUsage::CopySrc, {value});
-
-        wgpu::ImageCopyBuffer imageCopyBuffer = {};
-        imageCopyBuffer.buffer = textureData;
-        imageCopyBuffer.layout.bytesPerRow = 256;
-
-        wgpu::ImageCopyTexture imageCopyTexture = {};
-        imageCopyTexture.texture = texture;
-
-        wgpu::Extent3D copySize = {1, 1, 1};
-
-        commandEncoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &copySize);
-        return texture;
-    };
-
-    std::vector<wgpu::BindGroupEntry> bgEntries;
-    static_assert(kMaxSampledTexturesPerShaderStage == kMaxSamplersPerShaderStage,
-                  "Please update this test");
-    for (uint32_t i = 0; i < kMaxSampledTexturesPerShaderStage; ++i) {
-        wgpu::Texture texture = CreateTextureWithRedData(
-            wgpu::TextureFormat::R8Unorm, expectedValue, wgpu::TextureUsage::TextureBinding);
-        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
-
-        interface << "@group(0) @binding(" << binding++ << ") "
-                  << "var tex" << i << " : texture_2d<f32>;\n";
-
-        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, device.CreateSampler(), nullptr});
-
-        interface << "@group(0) @binding(" << binding++ << ")"
-                  << "var samp" << i << " : sampler;\n";
-
-        body << "if (abs(textureSampleLevel(tex" << i << ", samp" << i
-             << ", vec2f(0.5, 0.5), 0.0).r - " << expectedValue++ << ".0 / 255.0) > 0.0001) {\n";
-        body << "    return;\n";
-        body << "}\n";
-    }
-    for (uint32_t i = 0; i < kMaxStorageTexturesPerShaderStage; ++i) {
-        wgpu::Texture texture = CreateTextureWithRedData(
-            wgpu::TextureFormat::R32Uint, expectedValue, wgpu::TextureUsage::StorageBinding);
-        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
-
-        interface << "@group(0) @binding(" << binding++ << ") "
-                  << "var image" << i << " : texture_storage_2d<r32uint, write>;\n";
-
-        body << "_ = image" << i << ";";
-    }
-
-    for (uint32_t i = 0; i < kMaxUniformBuffersPerShaderStage; ++i) {
-        wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
-            device, wgpu::BufferUsage::Uniform, {expectedValue, 0, 0, 0});
-        bgEntries.push_back({nullptr, binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr});
-
-        interface << "struct UniformBuffer" << i << R"({
-                value : u32
-            }
-        )";
-        interface << "@group(0) @binding(" << binding++ << ") "
-                  << "var<uniform> ubuf" << i << " : UniformBuffer" << i << ";\n";
-
-        body << "if (ubuf" << i << ".value != " << expectedValue++ << "u) {\n";
-        body << "    return;\n";
-        body << "}\n";
-    }
-    // Save one storage buffer for writing the result
-    for (uint32_t i = 0; i < kMaxStorageBuffersPerShaderStage - 1; ++i) {
-        wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
-            device, wgpu::BufferUsage::Storage, {expectedValue});
-        bgEntries.push_back({nullptr, binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr});
-
-        interface << "struct ReadOnlyStorageBuffer" << i << R"({
-                value : u32
-            }
-        )";
-        interface << "@group(0) @binding(" << binding++ << ") "
-                  << "var<storage, read> sbuf" << i << " : ReadOnlyStorageBuffer" << i << ";\n";
-
-        body << "if (sbuf" << i << ".value != " << expectedValue++ << "u) {\n";
-        body << "    return;\n";
-        body << "}\n";
-    }
-
-    wgpu::Buffer result = utils::CreateBufferFromData<uint32_t>(
-        device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0});
-    bgEntries.push_back({nullptr, binding, result, 0, sizeof(uint32_t), nullptr, nullptr});
-
-    interface << R"(struct ReadWriteStorageBuffer{
-            value : u32
-        }
-    )";
-    interface << "@group(0) @binding(" << binding++ << ") "
-              << "var<storage, read_write> result : ReadWriteStorageBuffer;\n";
-
-    body << "result.value = 1u;\n";
-
-    std::string shader =
-        interface.str() + "@compute @workgroup_size(1) fn main() {\n" + body.str() + "}\n";
-    wgpu::ComputePipelineDescriptor cpDesc;
-    cpDesc.compute.module = utils::CreateShaderModule(device, shader.c_str());
-    cpDesc.compute.entryPoint = "main";
-    wgpu::ComputePipeline cp = device.CreateComputePipeline(&cpDesc);
-
-    wgpu::BindGroupDescriptor bgDesc = {};
-    bgDesc.layout = cp.GetBindGroupLayout(0);
-    bgDesc.entryCount = static_cast<uint32_t>(bgEntries.size());
-    bgDesc.entries = bgEntries.data();
-
-    wgpu::BindGroup bg = device.CreateBindGroup(&bgDesc);
-
-    wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
-    pass.SetPipeline(cp);
-    pass.SetBindGroup(0, bg);
-    pass.DispatchWorkgroups(1, 1, 1);
-    pass.End();
-
-    wgpu::CommandBuffer commands = commandEncoder.Finish();
-    queue.Submit(1, &commands);
-
-    EXPECT_BUFFER_U32_EQ(1, result, 0);
-}
-
 // This is a regression test for crbug.com/dawn/319 where creating a bind group with a
 // destroyed resource would crash the backend.
 TEST_P(BindGroupTests, CreateWithDestroyedResource) {
diff --git a/src/dawn/tests/end2end/MaxLimitTests.cpp b/src/dawn/tests/end2end/MaxLimitTests.cpp
index 2e3320e..1654bb9 100644
--- a/src/dawn/tests/end2end/MaxLimitTests.cpp
+++ b/src/dawn/tests/end2end/MaxLimitTests.cpp
@@ -392,6 +392,152 @@
     EXPECT_TEXTURE_EQ(&expected, renderTarget, {0, 0}, {1, 1});
 }
 
+// Test that creating a large bind group, with each binding type at the max count, works and can be
+// used correctly. The test loads a different value from each binding, and writes 1 to a storage
+// buffer if all values are correct.
+TEST_P(MaxLimitTests, ReallyLargeBindGroup) {
+    DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
+    wgpu::Limits limits = GetSupportedLimits().limits;
+
+    std::ostringstream interface;
+    std::ostringstream body;
+    uint32_t binding = 0;
+    uint32_t expectedValue = 42;
+
+    wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+
+    auto CreateTextureWithRedData = [&](wgpu::TextureFormat format, uint32_t value,
+                                        wgpu::TextureUsage usage) {
+        wgpu::TextureDescriptor textureDesc = {};
+        textureDesc.usage = wgpu::TextureUsage::CopyDst | usage;
+        textureDesc.size = {1, 1, 1};
+        textureDesc.format = format;
+        wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+        if (format == wgpu::TextureFormat::R8Unorm) {
+            ASSERT(expectedValue < 255u);
+        }
+        wgpu::Buffer textureData =
+            utils::CreateBufferFromData(device, wgpu::BufferUsage::CopySrc, {value});
+
+        wgpu::ImageCopyBuffer imageCopyBuffer = {};
+        imageCopyBuffer.buffer = textureData;
+        imageCopyBuffer.layout.bytesPerRow = 256;
+
+        wgpu::ImageCopyTexture imageCopyTexture = {};
+        imageCopyTexture.texture = texture;
+
+        wgpu::Extent3D copySize = {1, 1, 1};
+
+        commandEncoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &copySize);
+        return texture;
+    };
+
+    std::vector<wgpu::BindGroupEntry> bgEntries;
+    for (uint32_t i = 0;
+         i < std::min(limits.maxSampledTexturesPerShaderStage, limits.maxSamplersPerShaderStage);
+         ++i) {
+        wgpu::Texture texture = CreateTextureWithRedData(
+            wgpu::TextureFormat::R8Unorm, expectedValue, wgpu::TextureUsage::TextureBinding);
+        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
+
+        interface << "@group(0) @binding(" << binding++ << ") "
+                  << "var tex" << i << " : texture_2d<f32>;\n";
+
+        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, device.CreateSampler(), nullptr});
+
+        interface << "@group(0) @binding(" << binding++ << ")"
+                  << "var samp" << i << " : sampler;\n";
+
+        body << "if (abs(textureSampleLevel(tex" << i << ", samp" << i
+             << ", vec2f(0.5, 0.5), 0.0).r - " << expectedValue++ << ".0 / 255.0) > 0.0001) {\n";
+        body << "    return;\n";
+        body << "}\n";
+    }
+    for (uint32_t i = 0; i < limits.maxStorageTexturesPerShaderStage; ++i) {
+        wgpu::Texture texture = CreateTextureWithRedData(
+            wgpu::TextureFormat::R32Uint, expectedValue, wgpu::TextureUsage::StorageBinding);
+        bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
+
+        interface << "@group(0) @binding(" << binding++ << ") "
+                  << "var image" << i << " : texture_storage_2d<r32uint, write>;\n";
+
+        body << "_ = image" << i << ";";
+    }
+
+    for (uint32_t i = 0; i < limits.maxUniformBuffersPerShaderStage; ++i) {
+        wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
+            device, wgpu::BufferUsage::Uniform, {expectedValue, 0, 0, 0});
+        bgEntries.push_back({nullptr, binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr});
+
+        interface << "struct UniformBuffer" << i << R"({
+                value : u32
+            }
+        )";
+        interface << "@group(0) @binding(" << binding++ << ") "
+                  << "var<uniform> ubuf" << i << " : UniformBuffer" << i << ";\n";
+
+        body << "if (ubuf" << i << ".value != " << expectedValue++ << "u) {\n";
+        body << "    return;\n";
+        body << "}\n";
+    }
+    // Save one storage buffer for writing the result
+    for (uint32_t i = 0; i < limits.maxStorageBuffersPerShaderStage - 1; ++i) {
+        wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
+            device, wgpu::BufferUsage::Storage, {expectedValue});
+        bgEntries.push_back({nullptr, binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr});
+
+        interface << "struct ReadOnlyStorageBuffer" << i << R"({
+                value : u32
+            }
+        )";
+        interface << "@group(0) @binding(" << binding++ << ") "
+                  << "var<storage, read> sbuf" << i << " : ReadOnlyStorageBuffer" << i << ";\n";
+
+        body << "if (sbuf" << i << ".value != " << expectedValue++ << "u) {\n";
+        body << "    return;\n";
+        body << "}\n";
+    }
+
+    wgpu::Buffer result = utils::CreateBufferFromData<uint32_t>(
+        device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0});
+    bgEntries.push_back({nullptr, binding, result, 0, sizeof(uint32_t), nullptr, nullptr});
+
+    interface << R"(struct ReadWriteStorageBuffer{
+            value : u32
+        }
+    )";
+    interface << "@group(0) @binding(" << binding++ << ") "
+              << "var<storage, read_write> result : ReadWriteStorageBuffer;\n";
+
+    body << "result.value = 1u;\n";
+
+    std::string shader =
+        interface.str() + "@compute @workgroup_size(1) fn main() {\n" + body.str() + "}\n";
+    wgpu::ComputePipelineDescriptor cpDesc;
+    cpDesc.compute.module = utils::CreateShaderModule(device, shader.c_str());
+    cpDesc.compute.entryPoint = "main";
+    wgpu::ComputePipeline cp = device.CreateComputePipeline(&cpDesc);
+
+    wgpu::BindGroupDescriptor bgDesc = {};
+    bgDesc.layout = cp.GetBindGroupLayout(0);
+    bgDesc.entryCount = static_cast<uint32_t>(bgEntries.size());
+    bgDesc.entries = bgEntries.data();
+
+    wgpu::BindGroup bg = device.CreateBindGroup(&bgDesc);
+
+    wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
+    pass.SetPipeline(cp);
+    pass.SetBindGroup(0, bg);
+    pass.DispatchWorkgroups(1, 1, 1);
+    pass.End();
+
+    wgpu::CommandBuffer commands = commandEncoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_BUFFER_U32_EQ(1, result, 0);
+}
+
 DAWN_INSTANTIATE_TEST(MaxLimitTests,
                       D3D12Backend(),
                       MetalBackend(),
diff --git a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
index 4e8c069..87c0f6f 100644
--- a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -1293,25 +1293,32 @@
         wgpu::BindGroupLayoutEntry otherEntry;
     };
 
+    wgpu::Limits limits = GetSupportedLimits().limits;
+
     std::array<TestInfo, 7> kTestInfos = {
-        TestInfo{kMaxSampledTexturesPerShaderStage, BGLEntryType(wgpu::TextureSampleType::Float),
+        TestInfo{limits.maxSampledTexturesPerShaderStage,
+                 BGLEntryType(wgpu::TextureSampleType::Float),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxSamplersPerShaderStage, BGLEntryType(wgpu::SamplerBindingType::Filtering),
+        TestInfo{limits.maxSamplersPerShaderStage,
+                 BGLEntryType(wgpu::SamplerBindingType::Filtering),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxSamplersPerShaderStage, BGLEntryType(wgpu::SamplerBindingType::Comparison),
+        TestInfo{limits.maxSamplersPerShaderStage,
+                 BGLEntryType(wgpu::SamplerBindingType::Comparison),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxStorageBuffersPerShaderStage, BGLEntryType(wgpu::BufferBindingType::Storage),
+        TestInfo{limits.maxStorageBuffersPerShaderStage,
+                 BGLEntryType(wgpu::BufferBindingType::Storage),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
         TestInfo{
-            kMaxStorageTexturesPerShaderStage,
+            limits.maxStorageTexturesPerShaderStage,
             BGLEntryType(wgpu::StorageTextureAccess::WriteOnly, wgpu::TextureFormat::RGBA8Unorm),
             BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxUniformBuffersPerShaderStage, BGLEntryType(wgpu::BufferBindingType::Uniform),
+        TestInfo{limits.maxUniformBuffersPerShaderStage,
+                 BGLEntryType(wgpu::BufferBindingType::Uniform),
                  BGLEntryType(wgpu::TextureSampleType::Float)},
         // External textures use multiple bindings (3 sampled textures, 1 sampler, 1 uniform buffer)
         // that count towards the per stage binding limits. The number of external textures are
         // currently restricted by the maximum number of sampled textures.
-        TestInfo{kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
+        TestInfo{limits.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
                  BGLEntryType(&utils::kExternalTextureBindingLayout),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)}};
 
@@ -1388,14 +1395,16 @@
         wgpu::BindGroupLayoutEntry otherEntry;
     };
 
+    wgpu::Limits limits = GetSupportedLimits().limits;
+
     std::array<TestInfo, 3> kTestInfos = {
-        TestInfo{kMaxSampledTexturesPerShaderStage, kSampledTexturesPerExternalTexture,
+        TestInfo{limits.maxSampledTexturesPerShaderStage, kSampledTexturesPerExternalTexture,
                  BGLEntryType(wgpu::TextureSampleType::Float),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxSamplersPerShaderStage, kSamplersPerExternalTexture,
+        TestInfo{limits.maxSamplersPerShaderStage, kSamplersPerExternalTexture,
                  BGLEntryType(wgpu::SamplerBindingType::Filtering),
                  BGLEntryType(wgpu::BufferBindingType::Uniform)},
-        TestInfo{kMaxUniformBuffersPerShaderStage, kUniformsPerExternalTexture,
+        TestInfo{limits.maxUniformBuffersPerShaderStage, kUniformsPerExternalTexture,
                  BGLEntryType(wgpu::BufferBindingType::Uniform),
                  BGLEntryType(wgpu::TextureSampleType::Float)},
     };
@@ -1480,8 +1489,10 @@
 
     // In this test, we use all the same shader stage. Ensure that this does not exceed the
     // per-stage limit.
-    ASSERT(limits.maxDynamicUniformBuffersPerPipelineLayout <= kMaxUniformBuffersPerShaderStage);
-    ASSERT(limits.maxDynamicStorageBuffersPerPipelineLayout <= kMaxStorageBuffersPerShaderStage);
+    ASSERT(limits.maxDynamicUniformBuffersPerPipelineLayout <=
+           limits.maxUniformBuffersPerShaderStage);
+    ASSERT(limits.maxDynamicStorageBuffersPerPipelineLayout <=
+           limits.maxStorageBuffersPerShaderStage);
 
     for (uint32_t i = 0; i < limits.maxDynamicUniformBuffersPerPipelineLayout; ++i) {
         maxUniformDB.push_back(utils::BindingLayoutEntryInitializationHelper(