Add dawn-texel-copy-buffer-row-alignment feature

The current alignment is always 256. This more likely causes the padding
gaps between the rows in the buffer, and incurs an extra copy in the
BlitTextureToBuffer helper. With the new feature this CL introduces, the
alignment is loosened to 4 bytes on D3D11 backend.

Bug: chromium:378361783
Change-Id: I7484a45b283f69021d14471edaa2a7d3d4dfd9ee
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/214478
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Jie A Chen <jie.a.chen@intel.com>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
diff --git a/docs/dawn/features/dawn_texel_copy_buffer_row_alignment.md b/docs/dawn/features/dawn_texel_copy_buffer_row_alignment.md
new file mode 100644
index 0000000..5c5b714
--- /dev/null
+++ b/docs/dawn/features/dawn_texel_copy_buffer_row_alignment.md
@@ -0,0 +1,11 @@
+# Dawn Texel Copy Buffer Row Alignment
+
+The `dawn-texel-copy-buffer-row-alignment` feature exposes the alignment restriction of `byetsPerRow` in `wgpu::TexelCopyBufferLayout`. Each backend may have its own min alignment value. Without this feature, the alignment must be 256 for all backends.
+
+Additional functionalities:
+ - Adds `wgpu::DawnTexelCopyBufferRowAlignmentLimits` as chained struct for `wgpu::SupportedLimits`. It has a member `minTexelCopyBufferRowAlignment` to indicate the alignment limit of the current device.
+
+
+Notes:
+ - Even with this feature enabled, the alignment clients actually use, still needs to respect 'bytes-per-texel-block', and should be the max of them.
+ - The feature currently is only available on D3D11 backend.
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index aa32f4d..c8b6ae8 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -1839,6 +1839,15 @@
             {"name": "max immediate data range byte size", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}
         ]
     },
+    "dawn texel copy buffer row alignment limits": {
+        "category": "structure",
+        "chained": "out",
+        "chain roots": ["supported limits"],
+        "tags": ["dawn"],
+        "members": [
+            {"name": "min texel copy buffer row alignment", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}
+        ]
+    },
     "required limits": {
         "category": "structure",
         "extensible": "in",
@@ -2501,7 +2510,8 @@
             {"value": 51, "name": "dawn load resolve texture", "tags": ["dawn"]},
             {"value": 52, "name": "dawn partial load resolve texture", "tags": ["dawn"]},
             {"value": 53, "name": "multi draw indirect", "tags": ["dawn"]},
-            {"value": 54, "name": "clip distances", "tags": ["dawn"]}
+            {"value": 54, "name": "clip distances", "tags": ["dawn"]},
+            {"value": 55, "name": "dawn texel copy buffer row alignment", "tags": ["dawn"]}
         ]
     },
     "filter mode": {
@@ -4047,8 +4057,8 @@
             {"value": 55, "name": "y cb cr vk descriptor", "tags": ["dawn"]},
             {"value": 56, "name": "shared texture memory a hardware buffer properties", "tags": ["dawn", "native"]},
             {"value": 57, "name": "a hardware buffer properties", "tags": ["dawn", "native"]},
-            {"value": 58, "name": "dawn experimental immediate data limits", "tags": ["dawn"]}
-
+            {"value": 58, "name": "dawn experimental immediate data limits", "tags": ["dawn"]},
+            {"value": 59, "name": "dawn texel copy buffer row alignment limits", "tags": ["dawn"]}
         ]
     },
     "texture": {
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 9968d8a..1941254 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -136,6 +136,22 @@
         immediateDataLimits->nextInChain = originalChain;
     }
 
+    if (auto* texelCopyBufferRowAlignmentLimits =
+            unpacked.Get<DawnTexelCopyBufferRowAlignmentLimits>()) {
+        wgpu::ChainedStructOut* originalChain = texelCopyBufferRowAlignmentLimits->nextInChain;
+        if (!mSupportedFeatures.IsEnabled(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) {
+            // If the feature is not enabled, minTexelCopyBufferRowAlignment is default-initialized
+            // to WGPU_LIMIT_U32_UNDEFINED.
+            *texelCopyBufferRowAlignmentLimits = DawnTexelCopyBufferRowAlignmentLimits{};
+        } else {
+            *texelCopyBufferRowAlignmentLimits =
+                mPhysicalDevice->GetLimits().texelCopyBufferRowAlignmentLimits;
+        }
+
+        // Recover origin chain.
+        texelCopyBufferRowAlignmentLimits->nextInChain = originalChain;
+    }
+
     return wgpu::Status::Success;
 }
 
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index ccdc97bc..cb5c66d 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -403,10 +403,15 @@
 MaybeError ValidateImageCopyBuffer(DeviceBase const* device,
                                    const ImageCopyBuffer& imageCopyBuffer) {
     DAWN_TRY(device->ValidateObject(imageCopyBuffer.buffer));
+    auto alignment = kTextureBytesPerRowAlignment;
+    if (device->HasFeature(Feature::DawnTexelCopyBufferRowAlignment)) {
+        alignment =
+            device->GetLimits().texelCopyBufferRowAlignmentLimits.minTexelCopyBufferRowAlignment;
+    }
     if (imageCopyBuffer.layout.bytesPerRow != wgpu::kCopyStrideUndefined) {
-        DAWN_INVALID_IF(imageCopyBuffer.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0,
+        DAWN_INVALID_IF(imageCopyBuffer.layout.bytesPerRow % alignment != 0,
                         "bytesPerRow (%u) is not a multiple of %u.",
-                        imageCopyBuffer.layout.bytesPerRow, kTextureBytesPerRowAlignment);
+                        imageCopyBuffer.layout.bytesPerRow, alignment);
     }
 
     return {};
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 0641cc1..8c51e42 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -380,6 +380,10 @@
     mLimits.experimentalImmediateDataLimits =
         GetPhysicalDevice()->GetLimits().experimentalImmediateDataLimits;
 
+    // Get texelCopyBufferRowAlignmentLimits from physical device
+    mLimits.texelCopyBufferRowAlignmentLimits =
+        GetPhysicalDevice()->GetLimits().texelCopyBufferRowAlignmentLimits;
+
     mFormatTable = BuildFormatTable(this);
 
     if (!descriptor->label.IsUndefined()) {
@@ -1888,6 +1892,21 @@
         immediateDataLimits->nextInChain = originalChain;
     }
 
+    if (auto* texelCopyBufferRowAlignmentLimits =
+            unpacked.Get<DawnTexelCopyBufferRowAlignmentLimits>()) {
+        wgpu::ChainedStructOut* originalChain = texelCopyBufferRowAlignmentLimits->nextInChain;
+        if (!HasFeature(Feature::DawnTexelCopyBufferRowAlignment)) {
+            // If the feature is not enabled, minTexelCopyBufferRowAlignment is default-initialized
+            // to WGPU_LIMIT_U32_UNDEFINED.
+            *texelCopyBufferRowAlignmentLimits = DawnTexelCopyBufferRowAlignmentLimits{};
+        } else {
+            *texelCopyBufferRowAlignmentLimits = mLimits.texelCopyBufferRowAlignmentLimits;
+        }
+
+        // Recover origin chain.
+        texelCopyBufferRowAlignmentLimits->nextInChain = originalChain;
+    }
+
     return wgpu::Status::Success;
 }
 
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 25a694c..6824952 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -384,8 +384,12 @@
     {Feature::ChromiumExperimentalImmediateData,
      {"Support the \"enable chromium_experimental_immediate_data;\" directive in WGSL.",
       "https://github.com/gpuweb/gpuweb/blob/main/proposals/push-constants.md",
-      FeatureInfo::FeatureState::Experimental}}};
-
+      FeatureInfo::FeatureState::Experimental}},
+    {Feature::DawnTexelCopyBufferRowAlignment,
+     {"Expose the min row alignment in buffer for texel copy operations.",
+      "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
+      "dawn_texel_copy_buffer_row_alignment.md",
+      FeatureInfo::FeatureState::Stable}}};
 }  // anonymous namespace
 
 void FeaturesSet::EnableFeature(Feature feature) {
diff --git a/src/dawn/native/Limits.h b/src/dawn/native/Limits.h
index 7056324..f6d895e 100644
--- a/src/dawn/native/Limits.h
+++ b/src/dawn/native/Limits.h
@@ -39,6 +39,7 @@
     Limits v1;
     DawnExperimentalSubgroupLimits experimentalSubgroupLimits;
     DawnExperimentalImmediateDataLimits experimentalImmediateDataLimits;
+    DawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits;
 };
 
 // Populate |limits| with the default limits.
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 784e8ba..b39631d 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -185,6 +185,8 @@
     if (formatSupport & D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW) {
         EnableFeature(Feature::BGRA8UnormStorage);
     }
+
+    EnableFeature(Feature::DawnTexelCopyBufferRowAlignment);
 }
 
 MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
@@ -265,6 +267,9 @@
     limits->v1.maxInterStageShaderComponents =
         limits->v1.maxInterStageShaderVariables * D3D11_PS_INPUT_REGISTER_COMPONENTS;
 
+    // The BlitTextureToBuffer helper requires the alignment to be 4.
+    limits->texelCopyBufferRowAlignmentLimits.minTexelCopyBufferRowAlignment = 4;
+
     return {};
 }
 
diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp
index 7400deb..c986b1a 100644
--- a/src/dawn/node/binding/Converter.cpp
+++ b/src/dawn/node/binding/Converter.cpp
@@ -1618,6 +1618,7 @@
         case wgpu::FeatureName::YCbCrVulkanSamplers:
         case wgpu::FeatureName::DawnLoadResolveTexture:
         case wgpu::FeatureName::DawnPartialLoadResolveTexture:
+        case wgpu::FeatureName::DawnTexelCopyBufferRowAlignment:
             return false;
     }
     return false;
diff --git a/src/dawn/tests/end2end/CopyTests.cpp b/src/dawn/tests/end2end/CopyTests.cpp
index 57dc411..7f4e1cd 100644
--- a/src/dawn/tests/end2end/CopyTests.cpp
+++ b/src/dawn/tests/end2end/CopyTests.cpp
@@ -202,20 +202,25 @@
         return textureData;
     }
 
-    static BufferSpec MinimumBufferSpec(uint32_t width,
-                                        uint32_t height,
-                                        uint32_t depth = 1,
-                                        wgpu::TextureFormat format = kDefaultFormat) {
+    static BufferSpec MinimumBufferSpec(
+        uint32_t width,
+        uint32_t height,
+        uint32_t depth = 1,
+        wgpu::TextureFormat format = kDefaultFormat,
+        uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment) {
         return MinimumBufferSpec({width, height, depth}, kStrideComputeDefault,
                                  depth == 1 ? wgpu::kCopyStrideUndefined : kStrideComputeDefault,
-                                 format);
+                                 format, textureBytesPerRowAlignment);
     }
 
-    static BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent,
-                                        uint32_t overrideBytesPerRow = kStrideComputeDefault,
-                                        uint32_t overrideRowsPerImage = kStrideComputeDefault,
-                                        wgpu::TextureFormat format = kDefaultFormat) {
-        uint32_t bytesPerRow = utils::GetMinimumBytesPerRow(format, copyExtent.width);
+    static BufferSpec MinimumBufferSpec(
+        wgpu::Extent3D copyExtent,
+        uint32_t overrideBytesPerRow = kStrideComputeDefault,
+        uint32_t overrideRowsPerImage = kStrideComputeDefault,
+        wgpu::TextureFormat format = kDefaultFormat,
+        uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment) {
+        uint32_t bytesPerRow =
+            utils::GetMinimumBytesPerRow(format, copyExtent.width, textureBytesPerRowAlignment);
         if (overrideBytesPerRow != kStrideComputeDefault) {
             bytesPerRow = overrideBytesPerRow;
         }
@@ -262,6 +267,13 @@
         TextureSpec() { format = GetParam().mTextureFormat; }
     };
 
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        if (!SupportsFeatures({wgpu::FeatureName::DawnTexelCopyBufferRowAlignment})) {
+            return {};
+        }
+        return {wgpu::FeatureName::DawnTexelCopyBufferRowAlignment};
+    }
+
     void SetUp() override {
         DawnTestWithParams<CopyTextureFormatParams>::SetUp();
 
@@ -288,14 +300,26 @@
                                GetParam().mTextureFormat == wgpu::TextureFormat::RG11B10Ufloat) &&
                               (IsD3D11() || IsOpenGLES()) && IsIntelGen12());
     }
-    static BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) {
-        return CopyTests::MinimumBufferSpec(width, height, depth, GetParam().mTextureFormat);
+    uint32_t GetTextureBytesPerRowAlignment() const {
+        if (!device.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) {
+            return kTextureBytesPerRowAlignment;
+        }
+        wgpu::SupportedLimits limits{};
+        wgpu::DawnTexelCopyBufferRowAlignmentLimits alignmentLimits{};
+        limits.nextInChain = &alignmentLimits;
+        device.GetLimits(&limits);
+        return alignmentLimits.minTexelCopyBufferRowAlignment;
     }
-    static BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent,
-                                        uint32_t overrideBytesPerRow = kStrideComputeDefault,
-                                        uint32_t overrideRowsPerImage = kStrideComputeDefault) {
+    BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) {
+        return CopyTests::MinimumBufferSpec(width, height, depth, GetParam().mTextureFormat,
+                                            GetTextureBytesPerRowAlignment());
+    }
+    BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent,
+                                 uint32_t overrideBytesPerRow = kStrideComputeDefault,
+                                 uint32_t overrideRowsPerImage = kStrideComputeDefault) {
         return CopyTests::MinimumBufferSpec(copyExtent, overrideBytesPerRow, overrideRowsPerImage,
-                                            GetParam().mTextureFormat);
+                                            GetParam().mTextureFormat,
+                                            GetTextureBytesPerRowAlignment());
     }
 
     void DoTest(
diff --git a/src/dawn/utils/TestUtils.cpp b/src/dawn/utils/TestUtils.cpp
index eeb7aa9..abafbf0 100644
--- a/src/dawn/utils/TestUtils.cpp
+++ b/src/dawn/utils/TestUtils.cpp
@@ -32,7 +32,6 @@
 #include <vector>
 
 #include "dawn/common/Assert.h"
-#include "dawn/common/Constants.h"
 #include "dawn/common/Math.h"
 #include "dawn/utils/TestUtils.h"
 #include "dawn/utils/TextureUtils.h"
@@ -53,11 +52,13 @@
                   << ", " << static_cast<int>(color.b) << ", " << static_cast<int>(color.a) << ")";
 }
 
-uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format, uint32_t width) {
+uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format,
+                               uint32_t width,
+                               uint32_t textureBytesPerRowAlignment) {
     const uint32_t bytesPerBlock = dawn::utils::GetTexelBlockSizeInBytes(format);
     const uint32_t blockWidth = dawn::utils::GetTextureFormatBlockWidth(format);
     DAWN_ASSERT(width % blockWidth == 0);
-    return Align(bytesPerBlock * (width / blockWidth), kTextureBytesPerRowAlignment);
+    return Align(bytesPerBlock * (width / blockWidth), textureBytesPerRowAlignment);
 }
 
 TextureDataCopyLayout GetTextureDataCopyLayoutForTextureAtLevel(wgpu::TextureFormat format,
diff --git a/src/dawn/utils/TestUtils.h b/src/dawn/utils/TestUtils.h
index cf453b4..0e4e8e9 100644
--- a/src/dawn/utils/TestUtils.h
+++ b/src/dawn/utils/TestUtils.h
@@ -33,6 +33,8 @@
 #include <functional>
 #include <ostream>
 
+#include "dawn/common/Constants.h"
+
 namespace dawn::utils {
 
 struct RGBA8 {
@@ -66,7 +68,9 @@
     wgpu::Extent3D mipSize;
 };
 
-uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format, uint32_t width);
+uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format,
+                               uint32_t width,
+                               uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment);
 TextureDataCopyLayout GetTextureDataCopyLayoutForTextureAtLevel(
     wgpu::TextureFormat format,
     wgpu::Extent3D textureSizeAtLevel0,
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 905a76b..985e497 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -112,6 +112,7 @@
         case WGPUFeatureName_SubgroupsF16:
         case WGPUFeatureName_ClipDistances:
         case WGPUFeatureName_ChromiumExperimentalImmediateData:
+        case WGPUFeatureName_DawnTexelCopyBufferRowAlignment:
             return true;
     }
 
diff --git a/src/dawn/wire/client/LimitsAndFeatures.cpp b/src/dawn/wire/client/LimitsAndFeatures.cpp
index 790fb8c..e5e4ce4 100644
--- a/src/dawn/wire/client/LimitsAndFeatures.cpp
+++ b/src/dawn/wire/client/LimitsAndFeatures.cpp
@@ -60,6 +60,13 @@
                 *experimentalImmediateDataLimits = mExperimentalImmediateDataLimits;
                 break;
             }
+            case (WGPUSType_DawnTexelCopyBufferRowAlignmentLimits): {
+                auto* texelCopyBufferRowAlignmentLimits =
+                    reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain);
+                // This assignment break the next field of WGPUChainedStructOut head.
+                *texelCopyBufferRowAlignmentLimits = mTexelCopyBufferRowAlignmentLimits;
+                break;
+            }
             default:
                 // Fail if unknown sType found.
                 return WGPUStatus_Error;
@@ -118,6 +125,13 @@
                 mExperimentalImmediateDataLimits.chain.next = nullptr;
                 break;
             }
+            case (WGPUSType_DawnTexelCopyBufferRowAlignmentLimits): {
+                auto* texelCopyBufferRowAlignmentLimits =
+                    reinterpret_cast<WGPUDawnTexelCopyBufferRowAlignmentLimits*>(chain);
+                mTexelCopyBufferRowAlignmentLimits = *texelCopyBufferRowAlignmentLimits;
+                mTexelCopyBufferRowAlignmentLimits.chain.next = nullptr;
+                break;
+            }
             default:
                 DAWN_UNREACHABLE();
         }
diff --git a/src/dawn/wire/client/LimitsAndFeatures.h b/src/dawn/wire/client/LimitsAndFeatures.h
index 3034407..69ee5e8 100644
--- a/src/dawn/wire/client/LimitsAndFeatures.h
+++ b/src/dawn/wire/client/LimitsAndFeatures.h
@@ -50,6 +50,7 @@
     WGPUSupportedLimits mLimits;
     WGPUDawnExperimentalSubgroupLimits mExperimentalSubgroupLimits;
     WGPUDawnExperimentalImmediateDataLimits mExperimentalImmediateDataLimits;
+    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 6a8a1c7..5aea82f 100644
--- a/src/dawn/wire/server/ServerAdapter.cpp
+++ b/src/dawn/wire/server/ServerAdapter.cpp
@@ -121,8 +121,9 @@
     cmd.featuresCount = features.size();
     cmd.features = features.data();
 
-    // Query and report the adapter limits, including DawnExperimentalSubgroupLimits and
-    // DawnExperimentalImmediateDataLimits. Reporting to client.
+    // Query and report the adapter limits, including DawnExperimentalSubgroupLimits,
+    // DawnExperimentalImmediateDataLimits and DawnTexelCopyBufferRowAlignmentLimits.
+    // Reporting to client.
     WGPUSupportedLimits limits = {};
 
     // Chained DawnExperimentalSubgroupLimits.
@@ -135,6 +136,11 @@
     experimentalImmediateDataLimits.chain.sType = WGPUSType_DawnExperimentalImmediateDataLimits;
     experimentalSubgroupLimits.chain.next = &experimentalImmediateDataLimits.chain;
 
+    // Chained DawnTexelCopyBufferRowAlignmentLimits.
+    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits = {};
+    texelCopyBufferRowAlignmentLimits.chain.sType = WGPUSType_DawnTexelCopyBufferRowAlignmentLimits;
+    experimentalImmediateDataLimits.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 210ce6e..8c97bbe 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -123,8 +123,8 @@
     mProcs.adapterGetInfo(adapter, &info);
     cmd.info = &info;
 
-    // Query and report the adapter limits, including DawnExperimentalSubgroupLimits and
-    // DawnExperimentalImmediateDataLimits.
+    // Query and report the adapter limits, including DawnExperimentalSubgroupLimits,
+    // DawnExperimentalImmediateDataLimits, and DawnTexelCopyBufferRowAlignmentLimits.
     WGPUSupportedLimits limits = {};
 
     WGPUDawnExperimentalSubgroupLimits experimentalSubgroupLimits = {};
@@ -136,6 +136,11 @@
     experimentalImmediateDataLimits.chain.sType = WGPUSType_DawnExperimentalImmediateDataLimits;
     experimentalSubgroupLimits.chain.next = &experimentalImmediateDataLimits.chain;
 
+    // Chained DawnTexelCopyBufferRowAlignmentLimits.
+    WGPUDawnTexelCopyBufferRowAlignmentLimits texelCopyBufferRowAlignmentLimits = {};
+    texelCopyBufferRowAlignmentLimits.chain.sType = WGPUSType_DawnTexelCopyBufferRowAlignmentLimits;
+    experimentalImmediateDataLimits.chain.next = &texelCopyBufferRowAlignmentLimits.chain;
+
     mProcs.adapterGetLimits(adapter, &limits);
     cmd.limits = &limits;