Reland "[dawn] Move Compat limits into an extension struct"

This is a reland of commit 7adb0440d5edb719d620f5f5dcac75e5f8bc955e

The necessary fix has been applied in Chromium:
https://chromium-review.googlesource.com/c/chromium/src/+/6653572

Original change's description:
> [dawn] Move Compat limits into an extension struct
>
> Following upstream webgpu.h, which never added these to the core struct.
>
> Note that aside from dawn.json which affects webgpu.h, there are no
> changes to Emdawnwebgpu because it didn't implement these limits yet.
>
> Bug: 416304914
> Change-Id: I44e47d25ab3d3f5e8c1595ce7378bff79961ae18
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/244294
> Commit-Queue: Kai Ninomiya <kainino@chromium.org>
> Reviewed-by: Loko Kung <lokokung@google.com>

Bug: 416304914
Change-Id: If424a85755b87b86cabb8d1a1009a7186fae306b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/247515
Auto-Submit: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
diff --git a/generator/templates/api.h b/generator/templates/api.h
index 8fd74e9..a9abbb3 100644
--- a/generator/templates/api.h
+++ b/generator/templates/api.h
@@ -44,6 +44,7 @@
 #define WGPU_BREAKING_CHANGE_STRING_VIEW_OUTPUT_STRUCTS
 #define WGPU_BREAKING_CHANGE_STRING_VIEW_CALLBACKS
 #define WGPU_BREAKING_CHANGE_QUEUE_WORK_DONE_CALLBACK_MESSAGE
+#define WGPU_BREAKING_CHANGE_COMPATIBILITY_MODE_LIMITS
 {% macro render_c_default_value(member) -%}
     {%- if member.annotation in ["*", "const*", "const*const*"] -%}
         //* Pointer types should always default to NULL.
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 727edcd..f8aac7e 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -1584,7 +1584,15 @@
             {"name": "max compute workgroup size y", "type": "uint32_t", "default": "limit u32 undefined"},
             {"name": "max compute workgroup size z", "type": "uint32_t", "default": "limit u32 undefined"},
             {"name": "max compute workgroups per dimension", "type": "uint32_t", "default": "limit u32 undefined"},
-            {"name": "max immediate size", "type": "uint32_t", "default": "limit u32 undefined"},
+            {"name": "max immediate size", "type": "uint32_t", "default": "limit u32 undefined"}
+        ]
+    },
+    "compatibility mode limits": {
+        "category": "structure",
+        "chained": "out",
+        "chain roots": ["limits"],
+        "tags": ["compat"],
+        "members": [
             {"name": "max storage buffers in vertex stage", "type": "uint32_t", "default": "limit u32 undefined"},
             {"name": "max storage textures in vertex stage", "type": "uint32_t", "default": "limit u32 undefined"},
             {"name": "max storage buffers in fragment stage", "type": "uint32_t", "default": "limit u32 undefined"},
@@ -3791,7 +3799,8 @@
             {"value": 10, "name": "surface color management", "tags": ["upstream", "emscripten"]},
             {"value": 11, "name": "request adapter WebXR options", "tags": ["upstream", "emscripten"]},
 
-            {"value": 0, "name": "texture binding view dimension descriptor", "tags": ["compat"]},
+            {"value": 0, "name": "compatibility mode limits", "tags": ["compat"]},
+            {"value": 1, "name": "texture binding view dimension descriptor", "tags": ["compat"]},
 
             {"value": 0, "name": "emscripten surface source canvas HTML selector", "tags": ["emscripten"]},
 
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index e29f811..e6a04e5 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -166,12 +166,12 @@
 
     uint32_t maxSampledTexturesPerShaderStage = limits.v1.maxSampledTexturesPerShaderStage;
     uint32_t maxSamplersPerShaderStage = limits.v1.maxSamplersPerShaderStage;
-    uint32_t maxStorageBuffersInFragmentStage = limits.v1.maxStorageBuffersInFragmentStage;
-    uint32_t maxStorageBuffersInVertexStage = limits.v1.maxStorageBuffersInVertexStage;
+    uint32_t maxStorageBuffersInFragmentStage = limits.compat.maxStorageBuffersInFragmentStage;
+    uint32_t maxStorageBuffersInVertexStage = limits.compat.maxStorageBuffersInVertexStage;
     uint32_t maxStorageBuffersPerShaderStage = limits.v1.maxStorageBuffersPerShaderStage;
     uint32_t maxUniformBuffersPerShaderStage = limits.v1.maxUniformBuffersPerShaderStage;
-    uint32_t maxStorageTexturesInFragmentStage = limits.v1.maxStorageTexturesInFragmentStage;
-    uint32_t maxStorageTexturesInVertexStage = limits.v1.maxStorageTexturesInVertexStage;
+    uint32_t maxStorageTexturesInFragmentStage = limits.compat.maxStorageTexturesInFragmentStage;
+    uint32_t maxStorageTexturesInVertexStage = limits.compat.maxStorageTexturesInVertexStage;
     uint32_t maxStorageTexturesPerShaderStage = limits.v1.maxStorageTexturesPerShaderStage;
     for (SingleShaderStage stage : IterateStages(kAllStages)) {
         uint32_t sampledTextureCount = bindingCounts.perStage[stage].sampledTextureCount;
@@ -261,14 +261,14 @@
                                 "number of storage buffers used in fragment stage (%u) exceeds "
                                 "maxStorageBuffersInFragmentStage (%u).%s",
                                 storageBufferCount, maxStorageBuffersInFragmentStage,
-                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
+                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                             maxStorageBuffersInFragmentStage,
                                                             storageBufferCount));
                 DAWN_INVALID_IF(storageTextureCount > maxStorageTexturesInFragmentStage,
                                 "number of storage textures used in fragment stage (%u) exceeds "
                                 "maxStorageTexturesInFragmentStage (%u).%s",
                                 storageTextureCount, maxStorageTexturesInFragmentStage,
-                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
+                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                             maxStorageTexturesInFragmentStage,
                                                             storageTextureCount));
                 break;
@@ -277,14 +277,14 @@
                                 "number of storage buffers used in vertex stage (%u) exceeds "
                                 "maxStorageBuffersInVertexStage (%u).%s",
                                 storageBufferCount, maxStorageBuffersInVertexStage,
-                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
+                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                             maxStorageBuffersInVertexStage,
                                                             storageBufferCount));
                 DAWN_INVALID_IF(storageTextureCount > maxStorageTexturesInVertexStage,
                                 "number of storage textures used in vertex stage (%u) exceeds "
                                 "maxStorageTexturesInVertexStage (%u).%s",
                                 storageTextureCount, maxStorageTexturesInVertexStage,
-                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
+                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                             maxStorageTexturesInVertexStage,
                                                             storageTextureCount));
                 break;
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 1124f13..9c32973 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -383,7 +383,7 @@
     // Handle maxXXXPerStage/maxXXXInStage.
     EnforceLimitSpecInvariants(&mLimits, effectiveFeatureLevel);
 
-    if (mLimits.v1.maxStorageBuffersInFragmentStage < 1) {
+    if (mLimits.compat.maxStorageBuffersInFragmentStage < 1) {
         // If there is no storage buffer in fragment stage, UseBlitForB2T is not possible.
         mToggles.ForceSet(Toggle::UseBlitForB2T, false);
     }
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index 086331e..a58c6b6 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -67,26 +67,26 @@
 // 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.
-//                                                                 compat      tier0       tier1
-#define LIMITS_RESOURCE_BINDINGS(X)                                                               \
-    X(v1, Maximum,   maxDynamicUniformBuffersPerPipelineLayout,         8,         8,         10) \
-    X(v1, Maximum,   maxDynamicStorageBuffersPerPipelineLayout,         4,         4,          8) \
-    X(v1, Maximum,            maxSampledTexturesPerShaderStage,        16,        16,         16) \
-    X(v1, Maximum,                   maxSamplersPerShaderStage,        16,        16,         16) \
-    X(v1, Maximum,            maxStorageTexturesPerShaderStage,         4,         4,          8) \
-    X(v1, Maximum,           maxStorageTexturesInFragmentStage,         4,         4,          8) \
-    X(v1, Maximum,             maxStorageTexturesInVertexStage,         0,         4,          8) \
-    X(v1, Maximum,             maxUniformBuffersPerShaderStage,        12,        12,         12)
+//                                                                     compat      tier0       tier1
+#define LIMITS_RESOURCE_BINDINGS(X)                                                                   \
+    X(v1,     Maximum,   maxDynamicUniformBuffersPerPipelineLayout,         8,         8,         10) \
+    X(v1,     Maximum,   maxDynamicStorageBuffersPerPipelineLayout,         4,         4,          8) \
+    X(v1,     Maximum,            maxSampledTexturesPerShaderStage,        16,        16,         16) \
+    X(v1,     Maximum,                   maxSamplersPerShaderStage,        16,        16,         16) \
+    X(v1,     Maximum,            maxStorageTexturesPerShaderStage,         4,         4,          8) \
+    X(compat, Maximum,           maxStorageTexturesInFragmentStage,         4,         4,          8) \
+    X(compat, Maximum,             maxStorageTexturesInVertexStage,         0,         4,          8) \
+    X(v1,     Maximum,             maxUniformBuffersPerShaderStage,        12,        12,         12)
 
 // Tiers for limits related to storage buffer bindings. Should probably be merged with
 // LIMITS_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_STORAGE_BUFFER_BINDINGS(X)                                                          \
-    X(v1, Maximum,             maxStorageBuffersPerShaderStage,         8,         8,          10) \
-    X(v1, Maximum,             maxStorageBuffersInFragmentStage,        4,         8,          10) \
-    X(v1, Maximum,             maxStorageBuffersInVertexStage,          0,         8,          10)
+#define LIMITS_STORAGE_BUFFER_BINDINGS(X)                                                              \
+    X(v1,     Maximum,             maxStorageBuffersPerShaderStage,         8,         8,          10) \
+    X(compat, Maximum,            maxStorageBuffersInFragmentStage,         4,         8,          10) \
+    X(compat, Maximum,              maxStorageBuffersInVertexStage,         0,         8,          10)
 
 // TODO(crbug.com/dawn/685):
 // These limits aren't really tiered and could probably be grouped better.
@@ -268,6 +268,11 @@
     out->v1 = **unpacked;
     out->v1.nextInChain = nullptr;
 
+    if (auto* compatibilityModeLimits = unpacked.Get<CompatibilityModeLimits>()) {
+        out->compat = *compatibilityModeLimits;
+        out->compat.nextInChain = nullptr;
+    }
+
     // TODO(crbug.com/378361783): Add validation and default values to support requiring limits for
     // DawnTexelCopyBufferRowAlignmentLimits. Test this, see old test removed here:
     // https://dawn-review.googlesource.com/c/dawn/+/240934/11/src/dawn/tests/unittests/native/LimitsTests.cpp#b269
@@ -293,6 +298,11 @@
     // copy required v1 limits.
     out->v1 = **unpacked;
     out->v1.nextInChain = nullptr;
+
+    if (auto* compatibilityModeLimits = unpacked.Get<CompatibilityModeLimits>()) {
+        out->compat = *compatibilityModeLimits;
+        out->compat.nextInChain = nullptr;
+    }
 }
 
 MaybeError ValidateLimits(const CombinedLimits& supportedLimits,
@@ -401,18 +411,19 @@
         std::min(limits->v1.maxStorageBuffersPerShaderStage, kMaxStorageBuffersPerShaderStage);
     limits->v1.maxStorageTexturesPerShaderStage =
         std::min(limits->v1.maxStorageTexturesPerShaderStage, kMaxStorageTexturesPerShaderStage);
-    limits->v1.maxStorageBuffersInVertexStage =
-        std::min(limits->v1.maxStorageBuffersInVertexStage, kMaxStorageBuffersPerShaderStage);
-    limits->v1.maxStorageTexturesInVertexStage =
-        std::min(limits->v1.maxStorageTexturesInVertexStage, kMaxStorageTexturesPerShaderStage);
-    limits->v1.maxStorageBuffersInFragmentStage =
-        std::min(limits->v1.maxStorageBuffersInFragmentStage, kMaxStorageBuffersPerShaderStage);
-    limits->v1.maxStorageTexturesInFragmentStage =
-        std::min(limits->v1.maxStorageTexturesInFragmentStage, kMaxStorageTexturesPerShaderStage);
     limits->v1.maxUniformBuffersPerShaderStage =
         std::min(limits->v1.maxUniformBuffersPerShaderStage, kMaxUniformBuffersPerShaderStage);
     limits->v1.maxImmediateSize =
         std::min(limits->v1.maxImmediateSize, kMaxSupportedImmediateDataBytes);
+    // Compat limits.
+    limits->compat.maxStorageBuffersInVertexStage =
+        std::min(limits->compat.maxStorageBuffersInVertexStage, kMaxStorageBuffersPerShaderStage);
+    limits->compat.maxStorageTexturesInVertexStage =
+        std::min(limits->compat.maxStorageTexturesInVertexStage, kMaxStorageTexturesPerShaderStage);
+    limits->compat.maxStorageBuffersInFragmentStage =
+        std::min(limits->compat.maxStorageBuffersInFragmentStage, kMaxStorageBuffersPerShaderStage);
+    limits->compat.maxStorageTexturesInFragmentStage = std::min(
+        limits->compat.maxStorageTexturesInFragmentStage, kMaxStorageTexturesPerShaderStage);
 
     // Additional enforcement for dependent limits.
     limits->v1.maxStorageBufferBindingSize =
@@ -428,12 +439,12 @@
     // and you request maxXXXInStage = 3 things work but, if you request
     // maxXXXInStage = 5 they'd fail because suddenly you're you'd also be required
     // to request maxXXXPerStage to 5. So, we auto-uprade the perStage limits.
-    limits->v1.maxStorageBuffersPerShaderStage =
-        Max(limits->v1.maxStorageBuffersPerShaderStage, limits->v1.maxStorageBuffersInVertexStage,
-            limits->v1.maxStorageBuffersInFragmentStage);
-    limits->v1.maxStorageTexturesPerShaderStage =
-        Max(limits->v1.maxStorageTexturesPerShaderStage, limits->v1.maxStorageTexturesInVertexStage,
-            limits->v1.maxStorageTexturesInFragmentStage);
+    limits->v1.maxStorageBuffersPerShaderStage = Max(
+        limits->v1.maxStorageBuffersPerShaderStage, limits->compat.maxStorageBuffersInVertexStage,
+        limits->compat.maxStorageBuffersInFragmentStage);
+    limits->v1.maxStorageTexturesPerShaderStage = Max(
+        limits->v1.maxStorageTexturesPerShaderStage, limits->compat.maxStorageTexturesInVertexStage,
+        limits->compat.maxStorageTexturesInFragmentStage);
 
     if (featureLevel != wgpu::FeatureLevel::Compatibility) {
         // In core mode the maxStorageXXXInYYYStage are always set to maxStorageXXXPerShaderStage
@@ -447,10 +458,13 @@
         //     device.limits.maxStorageBuffersPerShaderStage = 5;
         //     It's ok to use 5 storage buffers in fragment stage because in core
         //     we originally only had maxStorageBuffersPerShaderStage
-        limits->v1.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
-        limits->v1.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
-        limits->v1.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
-        limits->v1.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
+        limits->compat.maxStorageBuffersInFragmentStage =
+            limits->v1.maxStorageBuffersPerShaderStage;
+        limits->compat.maxStorageTexturesInFragmentStage =
+            limits->v1.maxStorageTexturesPerShaderStage;
+        limits->compat.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
+        limits->compat.maxStorageTexturesInVertexStage =
+            limits->v1.maxStorageTexturesPerShaderStage;
     }
 }
 
@@ -463,10 +477,18 @@
     {
         wgpu::ChainedStructOut* originalChain = unpacked->nextInChain;
         **unpacked = combinedLimits.v1;
-        // Recover origin chain.
+        // Recover original chain.
         unpacked->nextInChain = originalChain;
     }
 
+    if (auto* compatibilityModeLimits = unpacked.Get<CompatibilityModeLimits>()) {
+        wgpu::ChainedStructOut* originalChain = compatibilityModeLimits->nextInChain;
+        *compatibilityModeLimits = combinedLimits.compat;
+
+        // Recover original chain.
+        compatibilityModeLimits->nextInChain = originalChain;
+    }
+
     if (auto* texelCopyBufferRowAlignmentLimits =
             unpacked.Get<DawnTexelCopyBufferRowAlignmentLimits>()) {
         wgpu::ChainedStructOut* originalChain = texelCopyBufferRowAlignmentLimits->nextInChain;
@@ -478,7 +500,7 @@
             *texelCopyBufferRowAlignmentLimits = combinedLimits.texelCopyBufferRowAlignmentLimits;
         }
 
-        // Recover origin chain.
+        // Recover original chain.
         texelCopyBufferRowAlignmentLimits->nextInChain = originalChain;
     }
 
@@ -493,7 +515,7 @@
                 combinedLimits.hostMappedPointerLimits.hostMappedPointerAlignment;
         }
 
-        // Recover origin chain.
+        // Recover original chain.
         hostMappedPointerLimits->nextInChain = originalChain;
     }
 
diff --git a/src/dawn/native/Limits.h b/src/dawn/native/Limits.h
index d87acdd..6a7bc6b 100644
--- a/src/dawn/native/Limits.h
+++ b/src/dawn/native/Limits.h
@@ -41,6 +41,7 @@
 // TODO(crbug.com/421950205): Replace this with dawn::utils::ComboLimits.
 struct CombinedLimits {
     Limits v1;
+    CompatibilityModeLimits compat;
     DawnHostMappedPointerLimits hostMappedPointerLimits;
     DawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits;
 };
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 3e27f1d..210dafb 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -238,15 +238,15 @@
     // Allocate half of the UAVs to storage buffers, and half to storage textures.
     limits->v1.maxStorageTexturesPerShaderStage = maxUAVsPerStage / 2;
     limits->v1.maxStorageBuffersPerShaderStage = maxUAVsPerStage / 2;
-    limits->v1.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
     // If the device only has feature level 11.0, technically, vertex stage doesn't have any UAV
     // slot (writable storage buffers). However, since Dawn spec requires that storage buffers must
     // be readonly in VS, it's safe to advertise that we have storage buffers in VS. Readonly
     // storage buffers will use SRV slots which are available in all stages.
     // The same for read-only storage textures in VS.
-    limits->v1.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
     limits->v1.maxSampledTexturesPerShaderStage = D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
     limits->v1.maxSamplersPerShaderStage = D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
     limits->v1.maxColorAttachments = D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT;
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index 6b94969..e8a8e03 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -297,10 +297,10 @@
     // Allocate half of the UAVs to storage buffers, and half to storage textures.
     limits->v1.maxStorageTexturesPerShaderStage = maxUAVsPerStage / 2;
     limits->v1.maxStorageBuffersPerShaderStage = maxUAVsPerStage - maxUAVsPerStage / 2;
-    limits->v1.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
-    limits->v1.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
     limits->v1.maxSampledTexturesPerShaderStage = maxSRVsPerStage;
     limits->v1.maxSamplersPerShaderStage = maxSamplersPerStage;
 
diff --git a/src/dawn/native/metal/PhysicalDeviceMTL.mm b/src/dawn/native/metal/PhysicalDeviceMTL.mm
index d6cd295..1fa71ae 100644
--- a/src/dawn/native/metal/PhysicalDeviceMTL.mm
+++ b/src/dawn/native/metal/PhysicalDeviceMTL.mm
@@ -896,10 +896,10 @@
     // - maxBindGroups
     // - maxVertexBufferArrayStride
 
-    limits->v1.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
-    limits->v1.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
-    limits->v1.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
 
     // The memory allocation must be in a single virtual memory (VM) region.
     limits->hostMappedPointerLimits.hostMappedPointerAlignment = 4096;
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index 4fc2c7f..1110457 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -345,14 +345,14 @@
     // image uniforms, so this isn't technically correct for vertex shaders.
     DAWN_TRY_ASSIGN(limits->v1.maxStorageTexturesPerShaderStage,
                     Get(gl, GL_MAX_COMPUTE_IMAGE_UNIFORMS));
-    DAWN_TRY_ASSIGN(limits->v1.maxStorageTexturesInFragmentStage,
+    DAWN_TRY_ASSIGN(limits->compat.maxStorageTexturesInFragmentStage,
                     Get(gl, GL_MAX_FRAGMENT_IMAGE_UNIFORMS));
 
-    DAWN_TRY_ASSIGN(limits->v1.maxStorageBuffersInFragmentStage,
+    DAWN_TRY_ASSIGN(limits->compat.maxStorageBuffersInFragmentStage,
                     Get(gl, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS));
-    DAWN_TRY_ASSIGN(limits->v1.maxStorageTexturesInVertexStage,
+    DAWN_TRY_ASSIGN(limits->compat.maxStorageTexturesInVertexStage,
                     Get(gl, GL_MAX_VERTEX_IMAGE_UNIFORMS));
-    DAWN_TRY_ASSIGN(limits->v1.maxStorageBuffersInVertexStage,
+    DAWN_TRY_ASSIGN(limits->compat.maxStorageBuffersInVertexStage,
                     Get(gl, GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS));
 
     DAWN_TRY_ASSIGN(limits->v1.maxUniformBuffersPerShaderStage,
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 3b5d0f8..0de3b3d 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -657,10 +657,10 @@
             std::min(baseLimits.v1.maxStorageBuffersPerShaderStage + extraResources,
                      vkLimits.maxPerStageDescriptorStorageBuffers);
     }
-    limits->v1.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
-    limits->v1.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
-    limits->v1.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInFragmentStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInFragmentStage = limits->v1.maxStorageBuffersPerShaderStage;
+    limits->compat.maxStorageTexturesInVertexStage = limits->v1.maxStorageTexturesPerShaderStage;
+    limits->compat.maxStorageBuffersInVertexStage = limits->v1.maxStorageBuffersPerShaderStage;
 
     CHECK_AND_SET_V1_MIN_LIMIT(minUniformBufferOffsetAlignment, minUniformBufferOffsetAlignment);
     CHECK_AND_SET_V1_MIN_LIMIT(minStorageBufferOffsetAlignment, minStorageBufferOffsetAlignment);
diff --git a/src/dawn/node/interop/DawnExtensions.idl b/src/dawn/node/interop/DawnExtensions.idl
index 276c294..3219de2 100644
--- a/src/dawn/node/interop/DawnExtensions.idl
+++ b/src/dawn/node/interop/DawnExtensions.idl
@@ -85,7 +85,6 @@
     GPUSize32 bindingArraySize = 1;
 };
 
-// TODO(crbug.com/354751907) Move to GPUAdapterInfo
 interface GPUSupportedLimits {
     readonly attribute unsigned long maxStorageBuffersInFragmentStage;
     readonly attribute unsigned long maxStorageTexturesInFragmentStage;
diff --git a/src/dawn/tests/unittests/native/LimitsTests.cpp b/src/dawn/tests/unittests/native/LimitsTests.cpp
index 48f298f..c8a96f5 100644
--- a/src/dawn/tests/unittests/native/LimitsTests.cpp
+++ b/src/dawn/tests/unittests/native/LimitsTests.cpp
@@ -66,10 +66,10 @@
     CombinedLimits reified = ReifyDefaultLimits(limits, wgpu::FeatureLevel::Core);
     EXPECT_EQ(reified.v1.maxComputeWorkgroupStorageSize, 16384u);
     EXPECT_EQ(reified.v1.maxStorageBufferBindingSize, 134217728ul);
-    EXPECT_EQ(reified.v1.maxStorageBuffersInFragmentStage, 8u);
-    EXPECT_EQ(reified.v1.maxStorageTexturesInFragmentStage, 4u);
-    EXPECT_EQ(reified.v1.maxStorageBuffersInVertexStage, 8u);
-    EXPECT_EQ(reified.v1.maxStorageTexturesInVertexStage, 4u);
+    EXPECT_EQ(reified.compat.maxStorageBuffersInFragmentStage, 8u);
+    EXPECT_EQ(reified.compat.maxStorageTexturesInFragmentStage, 4u);
+    EXPECT_EQ(reified.compat.maxStorageBuffersInVertexStage, 8u);
+    EXPECT_EQ(reified.compat.maxStorageTexturesInVertexStage, 4u);
 }
 
 // Test |ReifyDefaultLimits| populates the default for wgpu::FeatureLevel::Compatibility
@@ -82,10 +82,10 @@
     CombinedLimits reified = ReifyDefaultLimits(limits, wgpu::FeatureLevel::Compatibility);
     EXPECT_EQ(reified.v1.maxTextureDimension1D, 4096u);
     EXPECT_EQ(reified.v1.maxStorageBufferBindingSize, 134217728ul);
-    EXPECT_EQ(reified.v1.maxStorageBuffersInFragmentStage, 4u);
-    EXPECT_EQ(reified.v1.maxStorageTexturesInFragmentStage, 4u);
-    EXPECT_EQ(reified.v1.maxStorageBuffersInVertexStage, 0u);
-    EXPECT_EQ(reified.v1.maxStorageTexturesInVertexStage, 0u);
+    EXPECT_EQ(reified.compat.maxStorageBuffersInFragmentStage, 4u);
+    EXPECT_EQ(reified.compat.maxStorageTexturesInFragmentStage, 4u);
+    EXPECT_EQ(reified.compat.maxStorageBuffersInVertexStage, 0u);
+    EXPECT_EQ(reified.compat.maxStorageTexturesInVertexStage, 0u);
 }
 
 // Test |ReifyDefaultLimits| clamps to the default if
@@ -174,6 +174,21 @@
     }
 }
 
+// Basic test of |ValidateAndUnpackLimitsIn|.
+TEST(Limits, ValidateAndUnpackLimitsIn) {
+    {
+        CompatibilityModeLimits requiredCompat{};
+        requiredCompat.maxStorageBuffersInFragmentStage = 1234u;
+
+        Limits required{.nextInChain = &requiredCompat};
+
+        CombinedLimits combined{};
+        EXPECT_TRUE(ValidateAndUnpackLimitsIn(&required, {}, &combined).IsSuccess());
+        EXPECT_EQ(combined.v1.maxStorageBuffersPerShaderStage, wgpu::kLimitU32Undefined);
+        EXPECT_EQ(combined.compat.maxStorageBuffersInFragmentStage, 1234u);
+    }
+}
+
 // Test that |ApplyLimitTiers| degrades limits to the next best tier.
 TEST(Limits, ApplyLimitTiers) {
     auto SetLimitsStorageBufferBindingSizeTier2 = [](CombinedLimits* limits) {
@@ -505,124 +520,124 @@
     // Test maxXXXInStage is raised to maxXXXPerStage in core
     {
         CombinedLimits limits;
-        limits.v1.maxStorageBuffersInFragmentStage = 1;
-        limits.v1.maxStorageBuffersInVertexStage = 2;
+        limits.compat.maxStorageBuffersInFragmentStage = 1;
+        limits.compat.maxStorageBuffersInVertexStage = 2;
         limits.v1.maxStorageBuffersPerShaderStage = 3;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 4;
-        limits.v1.maxStorageTexturesInVertexStage = 5;
+        limits.compat.maxStorageTexturesInFragmentStage = 4;
+        limits.compat.maxStorageTexturesInVertexStage = 5;
         limits.v1.maxStorageTexturesPerShaderStage = 6;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Core);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 3u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 3u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 6u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 6u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
     }
 
     // Test maxXXXInStage are not raised to maxXXXPerStage in compat
     {
         CombinedLimits limits;
-        limits.v1.maxStorageBuffersInFragmentStage = 1;
-        limits.v1.maxStorageBuffersInVertexStage = 2;
+        limits.compat.maxStorageBuffersInFragmentStage = 1;
+        limits.compat.maxStorageBuffersInVertexStage = 2;
         limits.v1.maxStorageBuffersPerShaderStage = 3;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 4;
-        limits.v1.maxStorageTexturesInVertexStage = 5;
+        limits.compat.maxStorageTexturesInFragmentStage = 4;
+        limits.compat.maxStorageTexturesInVertexStage = 5;
         limits.v1.maxStorageTexturesPerShaderStage = 6;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Compatibility);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 1u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 2u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 1u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 2u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 4u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 5u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 4u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 5u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
     }
 
     // Test maxXXXPerStage is raised to maxXXXInStage in core
     {
         CombinedLimits limits;
-        limits.v1.maxStorageBuffersInFragmentStage = 3;
-        limits.v1.maxStorageBuffersInVertexStage = 2;
+        limits.compat.maxStorageBuffersInFragmentStage = 3;
+        limits.compat.maxStorageBuffersInVertexStage = 2;
         limits.v1.maxStorageBuffersPerShaderStage = 1;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 6;
-        limits.v1.maxStorageTexturesInVertexStage = 5;
+        limits.compat.maxStorageTexturesInFragmentStage = 6;
+        limits.compat.maxStorageTexturesInVertexStage = 5;
         limits.v1.maxStorageTexturesPerShaderStage = 4;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Core);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 3u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 3u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 6u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 6u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
 
-        limits.v1.maxStorageBuffersInFragmentStage = 2;
-        limits.v1.maxStorageBuffersInVertexStage = 3;
+        limits.compat.maxStorageBuffersInFragmentStage = 2;
+        limits.compat.maxStorageBuffersInVertexStage = 3;
         limits.v1.maxStorageBuffersPerShaderStage = 1;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 5;
-        limits.v1.maxStorageTexturesInVertexStage = 6;
+        limits.compat.maxStorageTexturesInFragmentStage = 5;
+        limits.compat.maxStorageTexturesInVertexStage = 6;
         limits.v1.maxStorageTexturesPerShaderStage = 4;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Core);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 3u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 3u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 6u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 6u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
     }
 
     // Test maxXXXPerStage is raised to maxXXXInStage compat
     {
         CombinedLimits limits;
-        limits.v1.maxStorageBuffersInFragmentStage = 3;
-        limits.v1.maxStorageBuffersInVertexStage = 2;
+        limits.compat.maxStorageBuffersInFragmentStage = 3;
+        limits.compat.maxStorageBuffersInVertexStage = 2;
         limits.v1.maxStorageBuffersPerShaderStage = 1;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 6;
-        limits.v1.maxStorageTexturesInVertexStage = 5;
+        limits.compat.maxStorageTexturesInFragmentStage = 6;
+        limits.compat.maxStorageTexturesInVertexStage = 5;
         limits.v1.maxStorageTexturesPerShaderStage = 4;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Compatibility);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 3u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 2u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 2u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 6u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 5u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 5u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
 
-        limits.v1.maxStorageBuffersInFragmentStage = 2;
-        limits.v1.maxStorageBuffersInVertexStage = 3;
+        limits.compat.maxStorageBuffersInFragmentStage = 2;
+        limits.compat.maxStorageBuffersInVertexStage = 3;
         limits.v1.maxStorageBuffersPerShaderStage = 1;
 
-        limits.v1.maxStorageTexturesInFragmentStage = 5;
-        limits.v1.maxStorageTexturesInVertexStage = 6;
+        limits.compat.maxStorageTexturesInFragmentStage = 5;
+        limits.compat.maxStorageTexturesInVertexStage = 6;
         limits.v1.maxStorageTexturesPerShaderStage = 4;
 
         EnforceLimitSpecInvariants(&limits, wgpu::FeatureLevel::Compatibility);
 
-        EXPECT_EQ(limits.v1.maxStorageBuffersInFragmentStage, 2u);
-        EXPECT_EQ(limits.v1.maxStorageBuffersInVertexStage, 3u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInFragmentStage, 2u);
+        EXPECT_EQ(limits.compat.maxStorageBuffersInVertexStage, 3u);
         EXPECT_EQ(limits.v1.maxStorageBuffersPerShaderStage, 3u);
 
-        EXPECT_EQ(limits.v1.maxStorageTexturesInFragmentStage, 5u);
-        EXPECT_EQ(limits.v1.maxStorageTexturesInVertexStage, 6u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInFragmentStage, 5u);
+        EXPECT_EQ(limits.compat.maxStorageTexturesInVertexStage, 6u);
         EXPECT_EQ(limits.v1.maxStorageTexturesPerShaderStage, 6u);
     }
 }
diff --git a/src/dawn/wire/client/LimitsAndFeatures.cpp b/src/dawn/wire/client/LimitsAndFeatures.cpp
index e5aa437..149bb14 100644
--- a/src/dawn/wire/client/LimitsAndFeatures.cpp
+++ b/src/dawn/wire/client/LimitsAndFeatures.cpp
@@ -46,18 +46,20 @@
         // Store the WGPUChainedStruct to restore the chain after assignment.
         WGPUChainedStruct originalChainedStruct = *chain;
         switch (chain->sType) {
-            case (WGPUSType_DawnTexelCopyBufferRowAlignmentLimits): {
-                auto* texelCopyBufferRowAlignmentLimits =
-                    reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain);
-                // This assignment break the next field of WGPUChainedStruct head.
-                *texelCopyBufferRowAlignmentLimits = mTexelCopyBufferRowAlignmentLimits;
+            case WGPUSType_CompatibilityModeLimits: {
+                *reinterpret_cast<WGPUCompatibilityModeLimits*>(chain) = mCompatLimits;
+                break;
+            }
+            case WGPUSType_DawnTexelCopyBufferRowAlignmentLimits: {
+                *reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain) =
+                    mTexelCopyBufferRowAlignmentLimits;
                 break;
             }
             default:
                 // Fail if unknown sType found.
                 return WGPUStatus_Error;
         }
-        // Restore the chain.
+        // Restore the chain (sType and next).
         *chain = originalChainedStruct;
     }
     return WGPUStatus_Success;
@@ -97,10 +99,17 @@
     // Handle other limits that chained after WGPUSupportedLimits
     for (auto* chain = limits->nextInChain; chain; chain = chain->next) {
         switch (chain->sType) {
-            case (WGPUSType_DawnTexelCopyBufferRowAlignmentLimits): {
-                auto* texelCopyBufferRowAlignmentLimits =
-                    reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain);
-                mTexelCopyBufferRowAlignmentLimits = *texelCopyBufferRowAlignmentLimits;
+            case WGPUSType_CompatibilityModeLimits: {
+                mCompatLimits = *reinterpret_cast<WGPUCompatibilityModeLimits*>(chain);
+                DAWN_ASSERT(mCompatLimits.chain.sType == WGPUSType_CompatibilityModeLimits);
+                mCompatLimits.chain.next = nullptr;
+                break;
+            }
+            case WGPUSType_DawnTexelCopyBufferRowAlignmentLimits: {
+                mTexelCopyBufferRowAlignmentLimits =
+                    *reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain);
+                DAWN_ASSERT(mTexelCopyBufferRowAlignmentLimits.chain.sType ==
+                            WGPUSType_DawnTexelCopyBufferRowAlignmentLimits);
                 mTexelCopyBufferRowAlignmentLimits.chain.next = nullptr;
                 break;
             }
diff --git a/src/dawn/wire/client/LimitsAndFeatures.h b/src/dawn/wire/client/LimitsAndFeatures.h
index ac0b7f2..f204372 100644
--- a/src/dawn/wire/client/LimitsAndFeatures.h
+++ b/src/dawn/wire/client/LimitsAndFeatures.h
@@ -47,7 +47,9 @@
     void SetFeatures(const WGPUFeatureName* features, uint32_t featuresCount);
 
   private:
+    // TODO(crbug.com/421950205): Use dawn::utils::ComboLimits here.
     WGPULimits mLimits;
+    WGPUCompatibilityModeLimits mCompatLimits;
     WGPUDawnTexelCopyBufferRowAlignmentLimits mTexelCopyBufferRowAlignmentLimits;
     absl::flat_hash_set<WGPUFeatureName> mFeatures;
 };
diff --git a/src/dawn/wire/server/ServerAdapter.cpp b/src/dawn/wire/server/ServerAdapter.cpp
index 7954178..420ccc1 100644
--- a/src/dawn/wire/server/ServerAdapter.cpp
+++ b/src/dawn/wire/server/ServerAdapter.cpp
@@ -115,12 +115,15 @@
     cmd.features = features.data();
 
     // Query and report the adapter limits, including all known extension limits.
+    // TODO(crbug.com/421950205): Use dawn::utils::ComboLimits here.
     WGPULimits limits = {};
-
+    // Chained CompatibilityModeLimits.
+    WGPUCompatibilityModeLimits compatLimits = WGPU_COMPATIBILITY_MODE_LIMITS_INIT;
+    limits.nextInChain = &compatLimits.chain;
     // Chained DawnTexelCopyBufferRowAlignmentLimits.
-    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits = {};
-    texelCopyBufferRowAlignmentLimits.chain.sType = WGPUSType_DawnTexelCopyBufferRowAlignmentLimits;
-    limits.nextInChain = &texelCopyBufferRowAlignmentLimits.chain;
+    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits =
+        WGPU_DAWN_TEXEL_COPY_BUFFER_ROW_ALIGNMENT_LIMITS_INIT;
+    compatLimits.chain.next = &texelCopyBufferRowAlignmentLimits.chain;
 
     mProcs.deviceGetLimits(device, &limits);
     cmd.limits = &limits;
diff --git a/src/dawn/wire/server/ServerInstance.cpp b/src/dawn/wire/server/ServerInstance.cpp
index abf237c..ce1e046 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -131,12 +131,16 @@
     cmd.info = &info;
 
     // Query and report the adapter limits, including all known extension limits.
+    // TODO(crbug.com/421950205): Use dawn::utils::ComboLimits here.
     WGPULimits limits = {};
-
+    // Chained CompatibilityModeLimits.
+    WGPUCompatibilityModeLimits compatLimits = WGPU_COMPATIBILITY_MODE_LIMITS_INIT;
+    compatLimits.chain.sType = WGPUSType_CompatibilityModeLimits;
+    limits.nextInChain = &compatLimits.chain;
     // Chained DawnTexelCopyBufferRowAlignmentLimits.
-    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits = {};
-    texelCopyBufferRowAlignmentLimits.chain.sType = WGPUSType_DawnTexelCopyBufferRowAlignmentLimits;
-    limits.nextInChain = &texelCopyBufferRowAlignmentLimits.chain;
+    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits =
+        WGPU_DAWN_TEXEL_COPY_BUFFER_ROW_ALIGNMENT_LIMITS_INIT;
+    compatLimits.chain.next = &texelCopyBufferRowAlignmentLimits.chain;
 
     mProcs.adapterGetLimits(adapter, &limits);
     cmd.limits = &limits;
diff --git a/third_party/webgpu-headers/webgpu.h.diff b/third_party/webgpu-headers/webgpu.h.diff
index 12c9dd4..bb1c9c9 100644
--- a/third_party/webgpu-headers/webgpu.h.diff
+++ b/third_party/webgpu-headers/webgpu.h.diff
@@ -8,6 +8,7 @@
 +#define WGPU_BREAKING_CHANGE_STRING_VIEW_OUTPUT_STRUCTS
 +#define WGPU_BREAKING_CHANGE_STRING_VIEW_CALLBACKS
 +#define WGPU_BREAKING_CHANGE_QUEUE_WORK_DONE_CALLBACK_MESSAGE
++#define WGPU_BREAKING_CHANGE_COMPATIBILITY_MODE_LIMITS
  
  #if defined(WGPU_SHARED_LIBRARY)
  #    if defined(_WIN32)
@@ -105,28 +106,6 @@
  })
  
 @@
-     uint32_t maxComputeWorkgroupSizeZ;
-     uint32_t maxComputeWorkgroupsPerDimension;
-     uint32_t maxImmediateSize;
-+    uint32_t maxStorageBuffersInVertexStage;
-+    uint32_t maxStorageTexturesInVertexStage;
-+    uint32_t maxStorageBuffersInFragmentStage;
-+    uint32_t maxStorageTexturesInFragmentStage;
- } WGPULimits WGPU_STRUCTURE_ATTRIBUTE;
- 
- #define WGPU_LIMITS_INIT _wgpu_MAKE_INIT_STRUCT(WGPULimits, { \
-@@
-     /*.maxComputeWorkgroupSizeZ=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-     /*.maxComputeWorkgroupsPerDimension=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-     /*.maxImmediateSize=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-+    /*.maxStorageBuffersInVertexStage=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-+    /*.maxStorageTexturesInVertexStage=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-+    /*.maxStorageBuffersInFragmentStage=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
-+    /*.maxStorageTexturesInFragmentStage=*/WGPU_LIMIT_U32_UNDEFINED _wgpu_COMMA \
- })
- 
- typedef struct WGPUMultisampleState {
-@@
      WGPUChainedStruct * nextInChain;
      WGPUStringView label;
      size_t bindGroupLayoutCount;