Compat: Add compat limits tier to dawn

Bug: dawn:2041
Change-Id: I154ac04e03028a769f65ac5944843db474087d8d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/151780
Commit-Queue: Gregg Tavares <gman@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index ffa524f..808f453 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -214,9 +214,10 @@
     }
 
     if (descriptor->requiredLimits != nullptr) {
-        mLimits.v1 = ReifyDefaultLimits(descriptor->requiredLimits->limits);
+        mLimits.v1 =
+            ReifyDefaultLimits(descriptor->requiredLimits->limits, adapter->GetFeatureLevel());
     } else {
-        GetDefaultLimits(&mLimits.v1);
+        GetDefaultLimits(&mLimits.v1, adapter->GetFeatureLevel());
     }
 
     mFormatTable = BuildFormatTable(this);
@@ -233,7 +234,7 @@
 }
 
 DeviceBase::DeviceBase() : mState(State::Alive), mToggles(ToggleStage::Device) {
-    GetDefaultLimits(&mLimits.v1);
+    GetDefaultLimits(&mLimits.v1, FeatureLevel::Core);
     mFormatTable = BuildFormatTable(this);
 }
 
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h
index 6d1ab59..9e1bd98 100644
--- a/src/dawn/native/Features.h
+++ b/src/dawn/native/Features.h
@@ -27,6 +27,8 @@
 
 namespace dawn::native {
 
+enum class FeatureLevel { Compatibility, Core };
+
 extern const ityp::array<Feature, FeatureInfo, kEnumCount<Feature>> kFeatureNameAndInfoList;
 
 wgpu::FeatureName ToAPI(Feature feature);
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index a8ee51b..defd731 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -24,67 +24,74 @@
 // clang-format off
 // TODO(crbug.com/dawn/685):
 // For now, only expose these tiers until metrics can determine better ones.
-#define LIMITS_WORKGROUP_STORAGE_SIZE(X)                                  \
-    X(Maximum, maxComputeWorkgroupStorageSize, 16384, 32768, 49152, 65536)
+//                                             compat tier0  tier1
+#define LIMITS_WORKGROUP_STORAGE_SIZE(X)                                         \
+    X(Maximum, maxComputeWorkgroupStorageSize, 16384, 16384, 32768, 49152, 65536)
 
 // Tiers for limits related to workgroup size.
 // TODO(crbug.com/dawn/685): Define these better. For now, use two tiers where one
 // is available on nearly all desktop platforms.
-#define LIMITS_WORKGROUP_SIZE(X)                                                   \
-    X(Maximum,           maxComputeInvocationsPerWorkgroup,       256,       1024) \
-    X(Maximum,                    maxComputeWorkgroupSizeX,       256,       1024) \
-    X(Maximum,                    maxComputeWorkgroupSizeY,       256,       1024) \
-    X(Maximum,                    maxComputeWorkgroupSizeZ,        64,         64) \
-    X(Maximum,            maxComputeWorkgroupsPerDimension,     65535,      65535)
+//                                                             compat        tier0       tier1
+#define LIMITS_WORKGROUP_SIZE(X)                                                                \
+    X(Maximum,           maxComputeInvocationsPerWorkgroup,       128,         256,       1024) \
+    X(Maximum,                    maxComputeWorkgroupSizeX,       128,         256,       1024) \
+    X(Maximum,                    maxComputeWorkgroupSizeY,       128,         256,       1024) \
+    X(Maximum,                    maxComputeWorkgroupSizeZ,        64,          64,         64) \
+    X(Maximum,            maxComputeWorkgroupsPerDimension,     65535,       65535,      65535)
 
 // Tiers are 128MB, 1GB, 2GB-4, 4GB-4.
-#define LIMITS_STORAGE_BUFFER_BINDING_SIZE(X)                                             \
-    X(Maximum, maxStorageBufferBindingSize, 134217728, 1073741824, 2147483644, 4294967292)
+//                                          compat     tier0      tier1
+#define LIMITS_STORAGE_BUFFER_BINDING_SIZE(X)                                                        \
+    X(Maximum, maxStorageBufferBindingSize, 134217728, 134217728, 1073741824, 2147483644, 4294967292)
 
 // Tiers are 256MB, 1GB, 2GB, 4GB.
-#define LIMITS_MAX_BUFFER_SIZE(X)                                             \
-    X(Maximum, maxBufferSize, 0x10000000, 0x40000000, 0x80000000, 0x100000000)
+//                            compat      tier0       tier1
+#define LIMITS_MAX_BUFFER_SIZE(X)                                                         \
+    X(Maximum, maxBufferSize, 0x10000000, 0x10000000, 0x40000000, 0x80000000, 0x100000000)
 
 // Tiers for limits related to resource bindings.
 // TODO(crbug.com/dawn/685): Define these better. For now, use two tiers where one
 // offers slightly better than default limits.
-#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)
+//                                                             compat      tier0       tier1
+#define LIMITS_RESOURCE_BINDINGS(X)                                                           \
+    X(Maximum,   maxDynamicUniformBuffersPerPipelineLayout,         8,         8,         10) \
+    X(Maximum,   maxDynamicStorageBuffersPerPipelineLayout,         4,         4,          8) \
+    X(Maximum,            maxSampledTexturesPerShaderStage,        16,        16,         16) \
+    X(Maximum,                   maxSamplersPerShaderStage,        16,        16,         16) \
+    X(Maximum,             maxStorageBuffersPerShaderStage,         4,         8,          8) \
+    X(Maximum,            maxStorageTexturesPerShaderStage,         4,         4,          8) \
+    X(Maximum,             maxUniformBuffersPerShaderStage,        12,        12,         12)
 
 // TODO(crbug.com/dawn/685):
 // These limits aren't really tiered and could probably be grouped better.
 // All Chrome platforms support 64 (iOS is 32) so there's no fingerprinting hazard in
 // extra additional buckets.
+//                                                             compat      tier0       tier1
 #define LIMITS_ATTACHMENTS(X)   \
-    X(Maximum,            maxColorAttachmentBytesPerSample,        32,         64)
-
+    X(Maximum,            maxColorAttachmentBytesPerSample,       32,         32,         64)
 
 // TODO(crbug.com/dawn/685):
 // These limits don't have tiers yet. Define two tiers with the same values since the macros
 // in this file expect more than one tier.
-#define LIMITS_OTHER(X)                                                            \
-    X(Maximum,                       maxTextureDimension1D,      8192,       8192) \
-    X(Maximum,                       maxTextureDimension2D,      8192,       8192) \
-    X(Maximum,                       maxTextureDimension3D,      2048,       2048) \
-    X(Maximum,                       maxTextureArrayLayers,       256,        256) \
-    X(Maximum,                               maxBindGroups,         4,          4) \
-    X(Maximum,              maxBindGroupsPlusVertexBuffers,        24,         24) \
-    X(Maximum,                     maxBindingsPerBindGroup,      1000,       1000) \
-    X(Maximum,                 maxUniformBufferBindingSize,     65536,      65536) \
-    X(Alignment,           minUniformBufferOffsetAlignment,       256,        256) \
-    X(Alignment,           minStorageBufferOffsetAlignment,       256,        256) \
-    X(Maximum,                            maxVertexBuffers,         8,          8) \
-    X(Maximum,                         maxVertexAttributes,        16,         16) \
-    X(Maximum,                  maxVertexBufferArrayStride,      2048,       2048) \
-    X(Maximum,               maxInterStageShaderComponents,        60,         60) \
-    X(Maximum,               maxInterStageShaderVariables,         16,         16) \
-    X(Maximum,                         maxColorAttachments,         8,          8)
+//                                                             compat      tier0       tier1
+#define LIMITS_OTHER(X)                                                                       \
+    X(Maximum,                       maxTextureDimension1D,      4096,      8192,       8192) \
+    X(Maximum,                       maxTextureDimension2D,      4096,      8192,       8192) \
+    X(Maximum,                       maxTextureDimension3D,      1024,      2048,       2048) \
+    X(Maximum,                       maxTextureArrayLayers,       256,       256,        256) \
+    X(Maximum,                               maxBindGroups,         4,         4,          4) \
+    X(Maximum,              maxBindGroupsPlusVertexBuffers,        24,        24,         24) \
+    X(Maximum,                     maxBindingsPerBindGroup,      1000,      1000,       1000) \
+    X(Maximum,                 maxUniformBufferBindingSize,     65536,     65536,      65536) \
+    X(Alignment,           minUniformBufferOffsetAlignment,       256,       256,        256) \
+    X(Alignment,           minStorageBufferOffsetAlignment,       256,       256,        256) \
+    X(Maximum,                            maxVertexBuffers,         8,         8,          8) \
+    X(Maximum,                         maxVertexAttributes,        16,        16,         16) \
+    X(Maximum,                  maxVertexBufferArrayStride,      2048,      2048,       2048) \
+    X(Maximum,               maxInterStageShaderComponents,        60,        60,         60) \
+    X(Maximum,                maxInterStageShaderVariables,        16,        16,         16) \
+    X(Maximum,                         maxColorAttachments,         4,         8,          8)
+
 // clang-format on
 
 #define LIMITS_EACH_GROUP(X)              \
@@ -179,23 +186,27 @@
 
 }  // namespace
 
-void GetDefaultLimits(Limits* limits) {
+void GetDefaultLimits(Limits* limits, FeatureLevel featureLevel) {
     DAWN_ASSERT(limits != nullptr);
-#define X(Better, limitName, base, ...) limits->limitName = base;
+#define X(Better, limitName, compat, base, ...) \
+    limits->limitName = featureLevel == FeatureLevel::Compatibility ? compat : base;
     LIMITS(X)
 #undef X
 }
 
-Limits ReifyDefaultLimits(const Limits& limits) {
+Limits ReifyDefaultLimits(const Limits& limits, FeatureLevel featureLevel) {
     Limits out;
-#define X(Class, limitName, base, ...)                                                         \
-    if (IsLimitUndefined(limits.limitName) ||                                                  \
-        CheckLimit<LimitClass::Class>::IsBetter(static_cast<decltype(limits.limitName)>(base), \
-                                                limits.limitName)) {                           \
-        /* If the limit is undefined or the default is better, use the default */              \
-        out.limitName = base;                                                                  \
-    } else {                                                                                   \
-        out.limitName = limits.limitName;                                                      \
+#define X(Class, limitName, compat, base, ...)                                         \
+    {                                                                                  \
+        const auto defaultLimit = static_cast<decltype(limits.limitName)>(             \
+            featureLevel == FeatureLevel::Compatibility ? compat : base);              \
+        if (IsLimitUndefined(limits.limitName) ||                                      \
+            CheckLimit<LimitClass::Class>::IsBetter(defaultLimit, limits.limitName)) { \
+            /* If the limit is undefined or the default is better, use the default */  \
+            out.limitName = defaultLimit;                                              \
+        } else {                                                                       \
+            out.limitName = limits.limitName;                                          \
+        }                                                                              \
     }
     LIMITS(X)
 #undef X
diff --git a/src/dawn/native/Limits.h b/src/dawn/native/Limits.h
index 266f2d2..83ae805 100644
--- a/src/dawn/native/Limits.h
+++ b/src/dawn/native/Limits.h
@@ -16,6 +16,7 @@
 #define SRC_DAWN_NATIVE_LIMITS_H_
 
 #include "dawn/native/Error.h"
+#include "dawn/native/Features.h"
 #include "dawn/native/VisitableMembers.h"
 #include "dawn/native/dawn_platform.h"
 
@@ -26,12 +27,12 @@
 };
 
 // Populate |limits| with the default limits.
-void GetDefaultLimits(Limits* limits);
+void GetDefaultLimits(Limits* limits, FeatureLevel featureLevel);
 
 // Returns a copy of |limits| where all undefined values are replaced
 // with their defaults. Also clamps to the defaults if the provided limits
 // are worse.
-Limits ReifyDefaultLimits(const Limits& limits);
+Limits ReifyDefaultLimits(const Limits& limits, FeatureLevel featureLevel);
 
 // Validate that |requiredLimits| are no better than |supportedLimits|.
 MaybeError ValidateLimits(const Limits& supportedLimits, const Limits& requiredLimits);
diff --git a/src/dawn/native/PhysicalDevice.cpp b/src/dawn/native/PhysicalDevice.cpp
index 2a95368..e9b813b 100644
--- a/src/dawn/native/PhysicalDevice.cpp
+++ b/src/dawn/native/PhysicalDevice.cpp
@@ -114,6 +114,13 @@
     }
 }
 
+void PhysicalDeviceBase::GetDefaultLimitsForSupportedFeatureLevel(Limits* limits) const {
+    // If the physical device does not support core then the defaults are compat defaults.
+    GetDefaultLimits(limits, SupportsFeatureLevel(FeatureLevel::Core)
+                                 ? FeatureLevel::Core
+                                 : FeatureLevel::Compatibility);
+}
+
 FeaturesSet PhysicalDeviceBase::GetSupportedFeatures(const TogglesState& toggles) const {
     FeaturesSet supportedFeaturesWithToggles;
     // Iterate each PhysicalDevice's supported feature and check if it is supported with given
diff --git a/src/dawn/native/PhysicalDevice.h b/src/dawn/native/PhysicalDevice.h
index 31e235b..6a436eb 100644
--- a/src/dawn/native/PhysicalDevice.h
+++ b/src/dawn/native/PhysicalDevice.h
@@ -34,8 +34,6 @@
 
 class DeviceBase;
 
-enum class FeatureLevel { Compatibility, Core };
-
 class PhysicalDeviceBase : public RefCounted {
   public:
     PhysicalDeviceBase(InstanceBase* instance, wgpu::BackendType backend);
@@ -104,6 +102,8 @@
     // Used for the tests that intend to use an adapter without all features enabled.
     void SetSupportedFeaturesForTesting(const std::vector<wgpu::FeatureName>& requiredFeatures);
 
+    void GetDefaultLimitsForSupportedFeatureLevel(Limits* limits) const;
+
   private:
     virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
                                                             const DeviceDescriptor* descriptor,
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index a609c86..ad55655 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -154,7 +154,7 @@
 }
 
 MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
-    GetDefaultLimits(&limits->v1);
+    GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
 
     // // https://docs.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels
 
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index e4dd949..2424039 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -193,7 +193,7 @@
             "devices.");
     }
 
-    GetDefaultLimits(&limits->v1);
+    GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
 
     // https://docs.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels
 
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index fb10665..434f8bb 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -725,7 +725,7 @@
             mtlLimits.*limitsForFamily.limit = limitsForFamily.values[mtlGPUFamily];
         }
 
-        GetDefaultLimits(&limits->v1);
+        GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
 
         limits->v1.maxTextureDimension1D = mtlLimits.max1DTextureSize;
         limits->v1.maxTextureDimension2D = mtlLimits.max2DTextureSize;
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 5518d47..9a1a231 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -63,7 +63,7 @@
 }
 
 MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
-    GetDefaultLimits(&limits->v1);
+    GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
     return {};
 }
 
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index 04be1d2..ba9f093 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -241,7 +241,7 @@
 
 MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
     const OpenGLFunctions& gl = mFunctions;
-    GetDefaultLimits(&limits->v1);
+    GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
 
     limits->v1.maxTextureDimension1D = limits->v1.maxTextureDimension2D =
         Get(gl, GL_MAX_TEXTURE_SIZE);
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 5603c26..e0ddd4c 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -355,7 +355,7 @@
 }
 
 MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
-    GetDefaultLimits(&limits->v1);
+    GetDefaultLimitsForSupportedFeatureLevel(&limits->v1);
     CombinedLimits baseLimits = *limits;
 
     const VkPhysicalDeviceLimits& vkLimits = mDeviceInfo.properties.limits;
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 0730897..b91ea80 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -302,7 +302,6 @@
     "unittests/ITypBitsetTests.cpp",
     "unittests/ITypSpanTests.cpp",
     "unittests/ITypVectorTests.cpp",
-    "unittests/LimitsTests.cpp",
     "unittests/LinkedListTests.cpp",
     "unittests/MathTests.cpp",
     "unittests/MutexProtectedTests.cpp",
@@ -335,6 +334,7 @@
     "unittests/native/DestroyObjectTests.cpp",
     "unittests/native/DeviceAsyncTaskTests.cpp",
     "unittests/native/DeviceCreationTests.cpp",
+    "unittests/native/LimitsTests.cpp",
     "unittests/native/ObjectContentHasherTests.cpp",
     "unittests/native/StreamTests.cpp",
     "unittests/validation/BindGroupValidationTests.cpp",
diff --git a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
index 5f71f01..0fcc98c 100644
--- a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
+++ b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
@@ -517,7 +517,7 @@
     // Test cases are split so that the attachments in each case do not exceed the default
     // maxColorAttachmentBytesPerSample.
     static std::vector<TestCase> kTestCases = {
-        // Full 8 attachment case (Signed 1 and 2 components).
+        // 6 attachment case (Signed 1 and 2 components).
         {AttachmentCase::Int(wgpu::TextureFormat::R32Sint, {kMaxInt32RepresentableInFloat, 0, 0, 0},
                              {kMaxInt32RepresentableInFloat, 0, 0, 0}),
          AttachmentCase::Int(wgpu::TextureFormat::R32Sint,
@@ -537,6 +537,21 @@
              {kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0},
              {kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0})},
 
+        // 4 attachment case (Signed 1 and 2 components).
+        {AttachmentCase::Int(wgpu::TextureFormat::R32Sint, {kMaxInt32RepresentableInFloat, 0, 0, 0},
+                             {kMaxInt32RepresentableInFloat, 0, 0, 0}),
+         AttachmentCase::Int(wgpu::TextureFormat::R32Sint,
+                             {kMaxInt32RepresentableInFloat + 1, 0, 0, 0},
+                             {kMaxInt32RepresentableInFloat + 1, 0, 0, 0}),
+         AttachmentCase::Int(
+             wgpu::TextureFormat::RG32Sint,
+             {kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0},
+             {kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0}),
+         AttachmentCase::Int(
+             wgpu::TextureFormat::RG32Sint,
+             {kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0},
+             {kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0})},
+
         // Signed 4 components.
         {AttachmentCase::Int(
              wgpu::TextureFormat::RGBA32Sint,
@@ -593,6 +608,9 @@
             expectedDataForRGBA32Float)}};
 
     for (const TestCase& testCase : kTestCases) {
+        if (testCase.size() > GetSupportedLimits().limits.maxColorAttachments) {
+            continue;
+        }
         std::vector<wgpu::Texture> textures;
         std::vector<wgpu::RenderPassColorAttachment> colorAttachmentsInfo;
         std::vector<wgpu::Buffer> outputBuffers;
diff --git a/src/dawn/tests/unittests/native/LimitsTests.cpp b/src/dawn/tests/unittests/native/LimitsTests.cpp
new file mode 100644
index 0000000..efc1596
--- /dev/null
+++ b/src/dawn/tests/unittests/native/LimitsTests.cpp
@@ -0,0 +1,474 @@
+// Copyright 2021 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 <gtest/gtest.h>
+
+#include "dawn/common/Constants.h"
+#include "dawn/native/Limits.h"
+
+namespace dawn {
+namespace native {
+
+// Test |GetDefaultLimits| returns the default for FeatureLeveL::Core.
+TEST(Limits, GetDefaultLimits) {
+    Limits limits = {};
+    EXPECT_NE(limits.maxBindGroups, 4u);
+
+    GetDefaultLimits(&limits, FeatureLevel::Core);
+
+    EXPECT_EQ(limits.maxBindGroups, 4u);
+}
+
+// Test |GetDefaultLimits| returns the default for FeatureLeveL::Compatibility.
+// Compatibility default limits are lower than Core.
+TEST(Limits, GetDefaultLimits_Compat) {
+    Limits limits = {};
+    EXPECT_NE(limits.maxColorAttachments, 4u);
+
+    GetDefaultLimits(&limits, FeatureLevel::Compatibility);
+
+    EXPECT_EQ(limits.maxColorAttachments, 4u);
+}
+
+// Test |ReifyDefaultLimits| populates the default for FeatureLevel::Core
+// if values are undefined.
+TEST(Limits, ReifyDefaultLimits_PopulatesDefault) {
+    Limits limits;
+    limits.maxComputeWorkgroupStorageSize = wgpu::kLimitU32Undefined;
+    limits.maxStorageBufferBindingSize = wgpu::kLimitU64Undefined;
+
+    Limits reified = ReifyDefaultLimits(limits, FeatureLevel::Core);
+    EXPECT_EQ(reified.maxComputeWorkgroupStorageSize, 16384u);
+    EXPECT_EQ(reified.maxStorageBufferBindingSize, 134217728ul);
+}
+
+// Test |ReifyDefaultLimits| populates the default for FeatureLevel::Compatibility
+// if values are undefined. Compatibility default limits are lower than Core.
+TEST(Limits, ReifyDefaultLimits_PopulatesDefault_Compat) {
+    Limits limits;
+    limits.maxTextureDimension1D = wgpu::kLimitU32Undefined;
+    limits.maxStorageBufferBindingSize = wgpu::kLimitU64Undefined;
+
+    Limits reified = ReifyDefaultLimits(limits, FeatureLevel::Compatibility);
+    EXPECT_EQ(reified.maxTextureDimension1D, 4096u);
+    EXPECT_EQ(reified.maxStorageBufferBindingSize, 134217728ul);
+}
+
+// Test |ReifyDefaultLimits| clamps to the default if
+// values are worse than the default.
+TEST(Limits, ReifyDefaultLimits_Clamps) {
+    Limits limits;
+    limits.maxStorageBuffersPerShaderStage = 4;
+    limits.minUniformBufferOffsetAlignment = 512;
+
+    Limits reified = ReifyDefaultLimits(limits, FeatureLevel::Core);
+    EXPECT_EQ(reified.maxStorageBuffersPerShaderStage, 8u);
+    EXPECT_EQ(reified.minUniformBufferOffsetAlignment, 256u);
+}
+
+// Test |ValidateLimits| works to validate limits are not better
+// than supported.
+TEST(Limits, ValidateLimits) {
+    // Start with the default for supported.
+    Limits defaults;
+    GetDefaultLimits(&defaults, FeatureLevel::Core);
+
+    // Test supported == required is valid.
+    {
+        Limits required = defaults;
+        EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
+    }
+
+    // Test supported == required is valid, when they are not default.
+    {
+        Limits supported = defaults;
+        Limits required = defaults;
+        supported.maxBindGroups += 1;
+        required.maxBindGroups += 1;
+        EXPECT_TRUE(ValidateLimits(supported, required).IsSuccess());
+    }
+
+    // Test that default-initialized (all undefined) is valid.
+    {
+        Limits required = {};
+        EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
+    }
+
+    // Test that better than supported is invalid for "maximum" limits.
+    {
+        Limits required = {};
+        required.maxTextureDimension3D = defaults.maxTextureDimension3D + 1;
+        MaybeError err = ValidateLimits(defaults, required);
+        EXPECT_TRUE(err.IsError());
+        err.AcquireError();
+    }
+
+    // Test that worse than supported is valid for "maximum" limits.
+    {
+        Limits required = {};
+        required.maxComputeWorkgroupSizeX = defaults.maxComputeWorkgroupSizeX - 1;
+        EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
+    }
+
+    // Test that better than min is invalid for "alignment" limits.
+    {
+        Limits required = {};
+        required.minUniformBufferOffsetAlignment = defaults.minUniformBufferOffsetAlignment / 2;
+        MaybeError err = ValidateLimits(defaults, required);
+        EXPECT_TRUE(err.IsError());
+        err.AcquireError();
+    }
+
+    // Test that worse than min and a power of two is valid for "alignment" limits.
+    {
+        Limits required = {};
+        required.minStorageBufferOffsetAlignment = defaults.minStorageBufferOffsetAlignment * 2;
+        EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
+    }
+
+    // Test that worse than min and not a power of two is invalid for "alignment" limits.
+    {
+        Limits required = {};
+        required.minStorageBufferOffsetAlignment = defaults.minStorageBufferOffsetAlignment * 3;
+        MaybeError err = ValidateLimits(defaults, required);
+        EXPECT_TRUE(err.IsError());
+        err.AcquireError();
+    }
+}
+
+// Test that |ApplyLimitTiers| degrades limits to the next best tier.
+TEST(Limits, ApplyLimitTiers) {
+    auto SetLimitsStorageBufferBindingSizeTier2 = [](Limits* limits) {
+        // Tier 2 of maxStorageBufferBindingSize is 1GB
+        limits->maxStorageBufferBindingSize = 1073741824;
+        // Also set the maxBufferSize to be large enough, as ApplyLimitTiers ensures tired
+        // maxStorageBufferBindingSize no larger than tiered maxBufferSize.
+        limits->maxBufferSize = 2147483648;
+    };
+    Limits limitsStorageBufferBindingSizeTier2;
+    GetDefaultLimits(&limitsStorageBufferBindingSizeTier2, FeatureLevel::Core);
+    SetLimitsStorageBufferBindingSizeTier2(&limitsStorageBufferBindingSizeTier2);
+
+    auto SetLimitsStorageBufferBindingSizeTier3 = [](Limits* limits) {
+        // Tier 3 of maxStorageBufferBindingSize is 2GB-4
+        limits->maxStorageBufferBindingSize = 2147483644;
+        // Also set the maxBufferSize to be large enough, as ApplyLimitTiers ensures tired
+        // maxStorageBufferBindingSize no larger than tiered maxBufferSize.
+        limits->maxBufferSize = 2147483648;
+    };
+    Limits limitsStorageBufferBindingSizeTier3;
+    GetDefaultLimits(&limitsStorageBufferBindingSizeTier3, FeatureLevel::Core);
+    SetLimitsStorageBufferBindingSizeTier3(&limitsStorageBufferBindingSizeTier3);
+
+    auto SetLimitsComputeWorkgroupStorageSizeTier1 = [](Limits* limits) {
+        limits->maxComputeWorkgroupStorageSize = 16384;
+    };
+    Limits limitsComputeWorkgroupStorageSizeTier1;
+    GetDefaultLimits(&limitsComputeWorkgroupStorageSizeTier1, FeatureLevel::Core);
+    SetLimitsComputeWorkgroupStorageSizeTier1(&limitsComputeWorkgroupStorageSizeTier1);
+
+    auto SetLimitsComputeWorkgroupStorageSizeTier3 = [](Limits* limits) {
+        limits->maxComputeWorkgroupStorageSize = 65536;
+    };
+    Limits limitsComputeWorkgroupStorageSizeTier3;
+    GetDefaultLimits(&limitsComputeWorkgroupStorageSizeTier3, FeatureLevel::Core);
+    SetLimitsComputeWorkgroupStorageSizeTier3(&limitsComputeWorkgroupStorageSizeTier3);
+
+    // Test that applying tiers to limits that are exactly
+    // equal to a tier returns the same values.
+    {
+        Limits limits = limitsStorageBufferBindingSizeTier2;
+        EXPECT_EQ(ApplyLimitTiers(limits), limits);
+
+        limits = limitsStorageBufferBindingSizeTier3;
+        EXPECT_EQ(ApplyLimitTiers(limits), limits);
+    }
+
+    // Test all limits slightly worse than tier 3.
+    {
+        Limits limits = limitsStorageBufferBindingSizeTier3;
+        limits.maxStorageBufferBindingSize -= 1;
+        EXPECT_EQ(ApplyLimitTiers(limits), limitsStorageBufferBindingSizeTier2);
+    }
+
+    // Test that limits may match one tier exactly and be degraded in another tier.
+    // Degrading to one tier does not affect the other tier.
+    {
+        Limits limits = limitsComputeWorkgroupStorageSizeTier3;
+        // Set tier 3 and change one limit to be insufficent.
+        SetLimitsStorageBufferBindingSizeTier3(&limits);
+        limits.maxStorageBufferBindingSize -= 1;
+
+        Limits tiered = ApplyLimitTiers(limits);
+
+        // Check that |tiered| has the limits of memorySize tier 2
+        Limits tieredWithMemorySizeTier2 = tiered;
+        SetLimitsStorageBufferBindingSizeTier2(&tieredWithMemorySizeTier2);
+        EXPECT_EQ(tiered, tieredWithMemorySizeTier2);
+
+        // Check that |tiered| has the limits of bindingSpace tier 3
+        Limits tieredWithBindingSpaceTier3 = tiered;
+        SetLimitsComputeWorkgroupStorageSizeTier3(&tieredWithBindingSpaceTier3);
+        EXPECT_EQ(tiered, tieredWithBindingSpaceTier3);
+    }
+
+    // Test that limits may be simultaneously degraded in two tiers independently.
+    {
+        Limits limits;
+        GetDefaultLimits(&limits, FeatureLevel::Core);
+        SetLimitsComputeWorkgroupStorageSizeTier3(&limits);
+        SetLimitsStorageBufferBindingSizeTier3(&limits);
+        limits.maxComputeWorkgroupStorageSize =
+            limitsComputeWorkgroupStorageSizeTier1.maxComputeWorkgroupStorageSize + 1;
+        limits.maxStorageBufferBindingSize =
+            limitsStorageBufferBindingSizeTier2.maxStorageBufferBindingSize + 1;
+
+        Limits tiered = ApplyLimitTiers(limits);
+
+        Limits expected = tiered;
+        SetLimitsComputeWorkgroupStorageSizeTier1(&expected);
+        SetLimitsStorageBufferBindingSizeTier2(&expected);
+        EXPECT_EQ(tiered, expected);
+    }
+}
+
+// Test that |ApplyLimitTiers| will hold the maxStorageBufferBindingSize no larger than
+// maxBufferSize restriction.
+TEST(Limits, TieredMaxStorageBufferBindingSizeNoLargerThanMaxBufferSize) {
+    // Start with the default for supported.
+    Limits defaults;
+    GetDefaultLimits(&defaults, FeatureLevel::Core);
+
+    // Test reported maxStorageBufferBindingSize around 128MB, 1GB, 2GB-4 and 4GB-4.
+    constexpr uint64_t storageSizeTier1 = 134217728ull;   // 128MB
+    constexpr uint64_t storageSizeTier2 = 1073741824ull;  // 1GB
+    constexpr uint64_t storageSizeTier3 = 2147483644ull;  // 2GB-4
+    constexpr uint64_t storageSizeTier4 = 4294967292ull;  // 4GB-4
+    constexpr uint64_t possibleReportedMaxStorageBufferBindingSizes[] = {
+        storageSizeTier1,     storageSizeTier1 + 1, storageSizeTier2 - 1, storageSizeTier2,
+        storageSizeTier2 + 1, storageSizeTier3 - 1, storageSizeTier3,     storageSizeTier3 + 1,
+        storageSizeTier4 - 1, storageSizeTier4,     storageSizeTier4 + 1};
+    // Test reported maxBufferSize around 256MB, 1GB, 2GB and 4GB, and a large 256GB.
+    constexpr uint64_t bufferSizeTier1 = 0x10000000ull;    // 256MB
+    constexpr uint64_t bufferSizeTier2 = 0x40000000ull;    // 1GB
+    constexpr uint64_t bufferSizeTier3 = 0x80000000ull;    // 2GB
+    constexpr uint64_t bufferSizeTier4 = 0x100000000ull;   // 4GB
+    constexpr uint64_t bufferSizeLarge = 0x4000000000ull;  // 256GB
+    constexpr uint64_t possibleReportedMaxBufferSizes[] = {
+        bufferSizeTier1,     bufferSizeTier1 + 1, bufferSizeTier2 - 1, bufferSizeTier2,
+        bufferSizeTier2 + 1, bufferSizeTier3 - 1, bufferSizeTier3,     bufferSizeTier3 + 1,
+        bufferSizeTier4 - 1, bufferSizeTier4,     bufferSizeTier4 + 1, bufferSizeLarge};
+
+    // Test that tiered maxStorageBufferBindingSize is no larger than tiered maxBufferSize.
+    for (uint64_t reportedMaxStorageBufferBindingSizes :
+         possibleReportedMaxStorageBufferBindingSizes) {
+        for (uint64_t reportedMaxBufferSizes : possibleReportedMaxBufferSizes) {
+            Limits limits = defaults;
+            limits.maxStorageBufferBindingSize = reportedMaxStorageBufferBindingSizes;
+            limits.maxBufferSize = reportedMaxBufferSizes;
+
+            Limits tiered = ApplyLimitTiers(limits);
+
+            EXPECT_LE(tiered.maxStorageBufferBindingSize, tiered.maxBufferSize);
+        }
+    }
+}
+
+// Test that |ApplyLimitTiers| will hold the maxUniformBufferBindingSize no larger than
+// maxBufferSize restriction.
+TEST(Limits, TieredMaxUniformBufferBindingSizeNoLargerThanMaxBufferSize) {
+    // Start with the default for supported.
+    Limits defaults;
+    GetDefaultLimits(&defaults, FeatureLevel::Core);
+
+    // Test reported maxStorageBufferBindingSize around 64KB, and a large 1GB.
+    constexpr uint64_t uniformSizeTier1 = 65536ull;       // 64KB
+    constexpr uint64_t uniformSizeLarge = 1073741824ull;  // 1GB
+    constexpr uint64_t possibleReportedMaxUniformBufferBindingSizes[] = {
+        uniformSizeTier1, uniformSizeTier1 + 1, uniformSizeLarge};
+    // Test reported maxBufferSize around 256MB, 1GB, 2GB and 4GB, and a large 256GB.
+    constexpr uint64_t bufferSizeTier1 = 0x10000000ull;    // 256MB
+    constexpr uint64_t bufferSizeTier2 = 0x40000000ull;    // 1GB
+    constexpr uint64_t bufferSizeTier3 = 0x80000000ull;    // 2GB
+    constexpr uint64_t bufferSizeTier4 = 0x100000000ull;   // 4GB
+    constexpr uint64_t bufferSizeLarge = 0x4000000000ull;  // 256GB
+    constexpr uint64_t possibleReportedMaxBufferSizes[] = {
+        bufferSizeTier1,     bufferSizeTier1 + 1, bufferSizeTier2 - 1, bufferSizeTier2,
+        bufferSizeTier2 + 1, bufferSizeTier3 - 1, bufferSizeTier3,     bufferSizeTier3 + 1,
+        bufferSizeTier4 - 1, bufferSizeTier4,     bufferSizeTier4 + 1, bufferSizeLarge};
+
+    // Test that tiered maxUniformBufferBindingSize is no larger than tiered maxBufferSize.
+    for (uint64_t reportedMaxUniformBufferBindingSizes :
+         possibleReportedMaxUniformBufferBindingSizes) {
+        for (uint64_t reportedMaxBufferSizes : possibleReportedMaxBufferSizes) {
+            Limits limits = defaults;
+            limits.maxUniformBufferBindingSize = reportedMaxUniformBufferBindingSizes;
+            limits.maxBufferSize = reportedMaxBufferSizes;
+
+            Limits tiered = ApplyLimitTiers(limits);
+
+            EXPECT_LE(tiered.maxUniformBufferBindingSize, tiered.maxBufferSize);
+        }
+    }
+}
+
+// Test |NormalizeLimits| works to enforce restriction of limits.
+TEST(Limits, NormalizeLimits) {
+    // Start with the default for supported.
+    Limits defaults;
+    GetDefaultLimits(&defaults, FeatureLevel::Core);
+
+    // Test specific limit values are clamped to internal Dawn constants.
+    {
+        Limits limits = defaults;
+        limits.maxVertexBufferArrayStride = kMaxVertexBufferArrayStride + 1;
+        limits.maxColorAttachments = uint32_t(kMaxColorAttachments) + 1;
+        limits.maxBindGroups = kMaxBindGroups + 1;
+        limits.maxBindGroupsPlusVertexBuffers = kMaxBindGroupsPlusVertexBuffers + 1;
+        limits.maxVertexAttributes = uint32_t(kMaxVertexAttributes) + 1;
+        limits.maxVertexBuffers = uint32_t(kMaxVertexBuffers) + 1;
+        limits.maxInterStageShaderComponents = kMaxInterStageShaderComponents + 1;
+        limits.maxSampledTexturesPerShaderStage = kMaxSampledTexturesPerShaderStage + 1;
+        limits.maxSamplersPerShaderStage = kMaxSamplersPerShaderStage + 1;
+        limits.maxStorageBuffersPerShaderStage = kMaxStorageBuffersPerShaderStage + 1;
+        limits.maxStorageTexturesPerShaderStage = kMaxStorageTexturesPerShaderStage + 1;
+        limits.maxUniformBuffersPerShaderStage = kMaxUniformBuffersPerShaderStage + 1;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxVertexBufferArrayStride, kMaxVertexBufferArrayStride);
+        EXPECT_EQ(limits.maxColorAttachments, uint32_t(kMaxColorAttachments));
+        EXPECT_EQ(limits.maxBindGroups, kMaxBindGroups);
+        EXPECT_EQ(limits.maxBindGroupsPlusVertexBuffers, kMaxBindGroupsPlusVertexBuffers);
+        EXPECT_EQ(limits.maxVertexAttributes, uint32_t(kMaxVertexAttributes));
+        EXPECT_EQ(limits.maxVertexBuffers, uint32_t(kMaxVertexBuffers));
+        EXPECT_EQ(limits.maxInterStageShaderComponents, kMaxInterStageShaderComponents);
+        EXPECT_EQ(limits.maxSampledTexturesPerShaderStage, kMaxSampledTexturesPerShaderStage);
+        EXPECT_EQ(limits.maxSamplersPerShaderStage, kMaxSamplersPerShaderStage);
+        EXPECT_EQ(limits.maxStorageBuffersPerShaderStage, kMaxStorageBuffersPerShaderStage);
+        EXPECT_EQ(limits.maxStorageTexturesPerShaderStage, kMaxStorageTexturesPerShaderStage);
+        EXPECT_EQ(limits.maxUniformBuffersPerShaderStage, kMaxUniformBuffersPerShaderStage);
+    }
+
+    // Test maxStorageBufferBindingSize is clamped to maxBufferSize.
+    // maxStorageBufferBindingSize is no larger than maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxStorageBufferBindingSize = reportedMaxBufferSize;
+        Limits limits = defaults;
+        limits.maxStorageBufferBindingSize = reportedMaxStorageBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxStorageBufferBindingSize, reportedMaxStorageBufferBindingSize);
+    }
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxStorageBufferBindingSize = reportedMaxBufferSize - 1;
+        Limits limits = defaults;
+        limits.maxStorageBufferBindingSize = reportedMaxStorageBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxStorageBufferBindingSize, reportedMaxStorageBufferBindingSize);
+    }
+    // maxStorageBufferBindingSize is equal to maxBufferSize+1, expect clamping to maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxStorageBufferBindingSize = reportedMaxBufferSize + 1;
+        Limits limits = defaults;
+        limits.maxStorageBufferBindingSize = reportedMaxStorageBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxStorageBufferBindingSize, reportedMaxBufferSize);
+    }
+    // maxStorageBufferBindingSize is much larger than maxBufferSize, expect clamping to
+    // maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxStorageBufferBindingSize = 4294967295;
+        Limits limits = defaults;
+        limits.maxStorageBufferBindingSize = reportedMaxStorageBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxStorageBufferBindingSize, reportedMaxBufferSize);
+    }
+
+    // Test maxUniformBufferBindingSize is clamped to maxBufferSize.
+    // maxUniformBufferBindingSize is no larger than maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxUniformBufferBindingSize = reportedMaxBufferSize - 1;
+        Limits limits = defaults;
+        limits.maxUniformBufferBindingSize = reportedMaxUniformBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxUniformBufferBindingSize, reportedMaxUniformBufferBindingSize);
+    }
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxUniformBufferBindingSize = reportedMaxBufferSize;
+        Limits limits = defaults;
+        limits.maxUniformBufferBindingSize = reportedMaxUniformBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxUniformBufferBindingSize, reportedMaxUniformBufferBindingSize);
+    }
+    // maxUniformBufferBindingSize is larger than maxBufferSize, expect clamping to maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxUniformBufferBindingSize = reportedMaxBufferSize + 1;
+        Limits limits = defaults;
+        limits.maxUniformBufferBindingSize = reportedMaxUniformBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxUniformBufferBindingSize, reportedMaxBufferSize);
+    }
+    // maxUniformBufferBindingSize is much larger than maxBufferSize, expect clamping to
+    // maxBufferSize
+    {
+        constexpr uint64_t reportedMaxBufferSize = 2147483648;
+        constexpr uint64_t reportedMaxUniformBufferBindingSize = 4294967295;
+        Limits limits = defaults;
+        limits.maxUniformBufferBindingSize = reportedMaxUniformBufferBindingSize;
+        limits.maxBufferSize = reportedMaxBufferSize;
+
+        NormalizeLimits(&limits);
+
+        EXPECT_EQ(limits.maxBufferSize, reportedMaxBufferSize);
+        EXPECT_EQ(limits.maxUniformBufferBindingSize, reportedMaxBufferSize);
+    }
+}
+
+}  // namespace native
+}  // namespace dawn