Validate that alignment limits are powers of two.

Also changes enums to use the concept of "limit class" from the WebGPU
specification.

Fixed: dawn:1242
Change-Id: I328ce98c2eaaf3f4b7ff1c253ee5f3db5a2980f5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/84762
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index a7b8ec9..3b20a1b 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -15,6 +15,7 @@
 #include "dawn/native/Limits.h"
 
 #include "dawn/common/Assert.h"
+#include "dawn/common/Math.h"
 
 #include <array>
 
@@ -22,39 +23,39 @@
 // TODO(crbug.com/dawn/685):
 // For now, only expose these tiers until metrics can determine better ones.
 #define LIMITS_WORKGROUP_STORAGE_SIZE(X)                                  \
-    X(Higher, maxComputeWorkgroupStorageSize, 16352, 32768, 49152, 65536)
+    X(Maximum, maxComputeWorkgroupStorageSize, 16352, 32768, 49152, 65536)
 
 #define LIMITS_STORAGE_BUFFER_BINDING_SIZE(X)                                             \
-    X(Higher, maxStorageBufferBindingSize, 134217728, 1073741824, 2147483647, 4294967295)
+    X(Maximum, maxStorageBufferBindingSize, 134217728, 1073741824, 2147483647, 4294967295)
 
 // 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(Higher,                     maxTextureDimension1D,  8192,  8192) \
-    X(Higher,                     maxTextureDimension2D,  8192,  8192) \
-    X(Higher,                     maxTextureDimension3D,  2048,  2048) \
-    X(Higher,                     maxTextureArrayLayers,   256,   256) \
-    X(Higher,                             maxBindGroups,     4,     4) \
-    X(Higher, maxDynamicUniformBuffersPerPipelineLayout,     8,     8) \
-    X(Higher, maxDynamicStorageBuffersPerPipelineLayout,     4,     4) \
-    X(Higher,          maxSampledTexturesPerShaderStage,    16,    16) \
-    X(Higher,                 maxSamplersPerShaderStage,    16,    16) \
-    X(Higher,           maxStorageBuffersPerShaderStage,     8,     8) \
-    X(Higher,          maxStorageTexturesPerShaderStage,     4,     4) \
-    X(Higher,           maxUniformBuffersPerShaderStage,    12,    12) \
-    X(Higher,               maxUniformBufferBindingSize, 65536, 65536) \
-    X( Lower,           minUniformBufferOffsetAlignment,   256,   256) \
-    X( Lower,           minStorageBufferOffsetAlignment,   256,   256) \
-    X(Higher,                          maxVertexBuffers,     8,     8) \
-    X(Higher,                       maxVertexAttributes,    16,    16) \
-    X(Higher,                maxVertexBufferArrayStride,  2048,  2048) \
-    X(Higher,             maxInterStageShaderComponents,    60,    60) \
-    X(Higher,         maxComputeInvocationsPerWorkgroup,   256,   256) \
-    X(Higher,                  maxComputeWorkgroupSizeX,   256,   256) \
-    X(Higher,                  maxComputeWorkgroupSizeY,   256,   256) \
-    X(Higher,                  maxComputeWorkgroupSizeZ,    64,    64) \
-    X(Higher,          maxComputeWorkgroupsPerDimension, 65535, 65535)
+#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,   maxDynamicUniformBuffersPerPipelineLayout,     8,     8) \
+    X(Maximum,   maxDynamicStorageBuffersPerPipelineLayout,     4,     4) \
+    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) \
+    X(Maximum,                            maxVertexBuffers,     8,     8) \
+    X(Maximum,                         maxVertexAttributes,    16,    16) \
+    X(Maximum,                  maxVertexBufferArrayStride,  2048,  2048) \
+    X(Maximum,               maxInterStageShaderComponents,    60,    60) \
+    X(Maximum,           maxComputeInvocationsPerWorkgroup,   256,   256) \
+    X(Maximum,                    maxComputeWorkgroupSizeX,   256,   256) \
+    X(Maximum,                    maxComputeWorkgroupSizeY,   256,   256) \
+    X(Maximum,                    maxComputeWorkgroupSizeZ,    64,    64) \
+    X(Maximum,            maxComputeWorkgroupsPerDimension, 65535, 65535)
 // clang-format on
 
 #define LIMITS_EACH_GROUP(X)              \
@@ -81,16 +82,16 @@
             return I;
         }
 
-        enum class LimitBetterDirection {
-            Lower,
-            Higher,
+        enum class LimitClass {
+            Alignment,
+            Maximum,
         };
 
-        template <LimitBetterDirection Better>
+        template <LimitClass C>
         struct CheckLimit;
 
         template <>
-        struct CheckLimit<LimitBetterDirection::Lower> {
+        struct CheckLimit<LimitClass::Alignment> {
             template <typename T>
             static bool IsBetter(T lhs, T rhs) {
                 return lhs < rhs;
@@ -101,12 +102,14 @@
                 DAWN_INVALID_IF(IsBetter(required, supported),
                                 "Required limit (%u) is lower than the supported limit (%u).",
                                 required, supported);
+                DAWN_INVALID_IF(!IsPowerOfTwo(required),
+                                "Required limit (%u) is not a power of two.", required);
                 return {};
             }
         };
 
         template <>
-        struct CheckLimit<LimitBetterDirection::Higher> {
+        struct CheckLimit<LimitClass::Maximum> {
             template <typename T>
             static bool IsBetter(T lhs, T rhs) {
                 return lhs > rhs;
@@ -148,9 +151,9 @@
 
     Limits ReifyDefaultLimits(const Limits& limits) {
         Limits out;
-#define X(Better, limitName, base, ...)                                           \
+#define X(Class, limitName, base, ...)                                           \
     if (IsLimitUndefined(limits.limitName) ||                                     \
-        CheckLimit<LimitBetterDirection::Better>::IsBetter(                       \
+        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;                                                     \
@@ -163,9 +166,9 @@
     }
 
     MaybeError ValidateLimits(const Limits& supportedLimits, const Limits& requiredLimits) {
-#define X(Better, limitName, ...)                                                  \
+#define X(Class, limitName, ...)                                                  \
     if (!IsLimitUndefined(requiredLimits.limitName)) {                             \
-        DAWN_TRY_CONTEXT(CheckLimit<LimitBetterDirection::Better>::Validate(       \
+        DAWN_TRY_CONTEXT(CheckLimit<LimitClass::Class>::Validate(       \
                              supportedLimits.limitName, requiredLimits.limitName), \
                          "validating " #limitName);                                \
     }
@@ -189,11 +192,11 @@
         }                                                            \
     }
 
-#define X_CHECK_BETTER_AND_CLAMP(Better, limitName, ...)                                       \
+#define X_CHECK_BETTER_AND_CLAMP(Class, limitName, ...)                                       \
     {                                                                                          \
         constexpr std::array<decltype(Limits::limitName), kTierCount> tiers{__VA_ARGS__};      \
         decltype(Limits::limitName) tierValue = tiers[i - 1];                                  \
-        if (CheckLimit<LimitBetterDirection::Better>::IsBetter(tierValue, limits.limitName)) { \
+        if (CheckLimit<LimitClass::Class>::IsBetter(tierValue, limits.limitName)) { \
             /* The tier is better. Go to the next tier. */                                     \
             continue;                                                                          \
         } else if (tierValue != limits.limitName) {                                            \
diff --git a/src/dawn/tests/unittests/LimitsTests.cpp b/src/dawn/tests/unittests/LimitsTests.cpp
index 544c0c5..3cc3cbe 100644
--- a/src/dawn/tests/unittests/LimitsTests.cpp
+++ b/src/dawn/tests/unittests/LimitsTests.cpp
@@ -78,7 +78,7 @@
         EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
     }
 
-    // Test that better than max is invalid.
+    // Test that better than supported is invalid for "maximum" limits.
     {
         dawn::native::Limits required = {};
         required.maxTextureDimension3D = defaults.maxTextureDimension3D + 1;
@@ -87,14 +87,14 @@
         err.AcquireError();
     }
 
-    // Test that worse than max is valid.
+    // Test that worse than supported is valid for "maximum" limits.
     {
         dawn::native::Limits required = {};
         required.maxComputeWorkgroupSizeX = defaults.maxComputeWorkgroupSizeX - 1;
         EXPECT_TRUE(ValidateLimits(defaults, required).IsSuccess());
     }
 
-    // Test that better than min is invalid.
+    // Test that better than min is invalid for "alignment" limits.
     {
         dawn::native::Limits required = {};
         required.minUniformBufferOffsetAlignment = defaults.minUniformBufferOffsetAlignment / 2;
@@ -103,12 +103,21 @@
         err.AcquireError();
     }
 
-    // Test that worse than min is valid.
+    // Test that worse than min and a power of two is valid for "alignment" limits.
     {
         dawn::native::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.
+    {
+        dawn::native::Limits required = {};
+        required.minStorageBufferOffsetAlignment = defaults.minStorageBufferOffsetAlignment * 3;
+        dawn::native::MaybeError err = ValidateLimits(defaults, required);
+        EXPECT_TRUE(err.IsError());
+        err.AcquireError();
+    }
 }
 
 // Test that |ApplyLimitTiers| degrades limits to the next best tier.