• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "tests/DawnTest.h"
16 
17 #include "common/Math.h"
18 #include "utils/WGPUHelpers.h"
19 
20 #include <array>
21 #include <functional>
22 
23 namespace {
24 
25     // Helper for replacing all occurrences of substr in str with replacement
ReplaceAll(std::string str,const std::string & substr,const std::string & replacement)26     std::string ReplaceAll(std::string str,
27                            const std::string& substr,
28                            const std::string& replacement) {
29         size_t pos = 0;
30         while ((pos = str.find(substr, pos)) != std::string::npos) {
31             str.replace(pos, substr.length(), replacement);
32             pos += replacement.length();
33         }
34         return str;
35     }
36 
37     // DataMatcherCallback is the callback function by DataMatcher.
38     // It is called for each contiguous sequence of bytes that should be checked
39     // for equality.
40     // offset and size are in units of bytes.
41     using DataMatcherCallback = std::function<void(uint32_t offset, uint32_t size)>;
42 
43     // DataMatcher is a function pointer to a data matching function.
44     // size is the total number of bytes being considered for matching.
45     // The callback may be called once or multiple times, and may only consider
46     // part of the interval [0, size)
47     using DataMatcher = void (*)(uint32_t size, DataMatcherCallback);
48 
49     // FullDataMatcher is a DataMatcher that calls callback with the interval
50     // [0, size)
FullDataMatcher(uint32_t size,DataMatcherCallback callback)51     void FullDataMatcher(uint32_t size, DataMatcherCallback callback) {
52         callback(0, size);
53     }
54 
55     // StridedDataMatcher is a DataMatcher that calls callback with the strided
56     // intervals of length BYTES_TO_MATCH, skipping BYTES_TO_SKIP.
57     // For example: StridedDataMatcher<2, 4>(18, callback) will call callback
58     // with the intervals: [0, 2), [6, 8), [12, 14)
59     template <int BYTES_TO_MATCH, int BYTES_TO_SKIP>
StridedDataMatcher(uint32_t size,DataMatcherCallback callback)60     void StridedDataMatcher(uint32_t size, DataMatcherCallback callback) {
61         uint32_t offset = 0;
62         while (offset < size) {
63             callback(offset, BYTES_TO_MATCH);
64             offset += BYTES_TO_MATCH + BYTES_TO_SKIP;
65         }
66     }
67 
68     // Align returns the WGSL decoration for an explicit structure field alignment
AlignDeco(uint32_t value)69     std::string AlignDeco(uint32_t value) {
70         return "[[align(" + std::to_string(value) + ")]] ";
71     }
72 
73 }  // namespace
74 
75 // Field holds test parameters for ComputeLayoutMemoryBufferTests.Fields
76 struct Field {
77     const char* type;  // Type of the field
78     uint32_t align;    // Alignment of the type in bytes
79     uint32_t size;     // Natural size of the type in bytes
80 
81     uint32_t padded_size = 0;                // Decorated (extended) size of the type in bytes
82     DataMatcher matcher = &FullDataMatcher;  // The matching method
83     bool storage_buffer_only = false;        // This should only be used for storage buffer tests
84 
85     // Sets the padded_size to value.
86     // Returns this Field so calls can be chained.
PaddedSizeField87     Field& PaddedSize(uint32_t value) {
88         padded_size = value;
89         return *this;
90     }
91 
92     // Sets the matcher to a StridedDataMatcher<BYTES_TO_MATCH, BYTES_TO_SKIP>.
93     // Returns this Field so calls can be chained.
94     template <int BYTES_TO_MATCH, int BYTES_TO_SKIP>
StridedField95     Field& Strided() {
96         matcher = &StridedDataMatcher<BYTES_TO_MATCH, BYTES_TO_SKIP>;
97         return *this;
98     }
99 
100     // Marks that this should only be used for storage buffer tests.
101     // Returns this Field so calls can be chained.
StorageBufferOnlyField102     Field& StorageBufferOnly() {
103         storage_buffer_only = true;
104         return *this;
105     }
106 };
107 
108 // StorageClass is an enumerator of storage classes used by ComputeLayoutMemoryBufferTests.Fields
109 enum class StorageClass {
110     Uniform,
111     Storage,
112 };
113 
operator <<(std::ostream & o,StorageClass storageClass)114 std::ostream& operator<<(std::ostream& o, StorageClass storageClass) {
115     switch (storageClass) {
116         case StorageClass::Uniform:
117             o << "uniform";
118             break;
119         case StorageClass::Storage:
120             o << "storage";
121             break;
122     }
123     return o;
124 }
125 
operator <<(std::ostream & o,Field field)126 std::ostream& operator<<(std::ostream& o, Field field) {
127     o << "[[align(" << field.align << "), size("
128       << (field.padded_size > 0 ? field.padded_size : field.size) << ")]] " << field.type;
129     return o;
130 }
131 
132 DAWN_TEST_PARAM_STRUCT(ComputeLayoutMemoryBufferTestParams, StorageClass, Field);
133 
134 class ComputeLayoutMemoryBufferTests
135     : public DawnTestWithParams<ComputeLayoutMemoryBufferTestParams> {
SetUp()136     void SetUp() override {
137         DawnTestBase::SetUp();
138     }
139 };
140 
TEST_P(ComputeLayoutMemoryBufferTests,Fields)141 TEST_P(ComputeLayoutMemoryBufferTests, Fields) {
142     // Sentinel value markers codes used to check that the start and end of
143     // structures are correctly aligned. Each of these codes are distinct and
144     // are not likely to be confused with data.
145     constexpr uint32_t kDataHeaderCode = 0xa0b0c0a0u;
146     constexpr uint32_t kDataFooterCode = 0x40302010u;
147     constexpr uint32_t kInputHeaderCode = 0x91827364u;
148     constexpr uint32_t kInputFooterCode = 0x19283764u;
149 
150     // Byte codes used for field padding. The MSB is set for each of these.
151     // The field data has the MSB 0.
152     constexpr uint8_t kDataAlignPaddingCode = 0xfeu;
153     constexpr uint8_t kFieldAlignPaddingCode = 0xfdu;
154     constexpr uint8_t kFieldSizePaddingCode = 0xdcu;
155     constexpr uint8_t kDataSizePaddingCode = 0xdbu;
156     constexpr uint8_t kInputFooterAlignPaddingCode = 0xdau;
157     constexpr uint8_t kInputTailPaddingCode = 0xd9u;
158 
159     // Status codes returned by the shader.
160     constexpr uint32_t kStatusBadInputHeader = 100u;
161     constexpr uint32_t kStatusBadInputFooter = 101u;
162     constexpr uint32_t kStatusBadDataHeader = 102u;
163     constexpr uint32_t kStatusBadDataFooter = 103u;
164     constexpr uint32_t kStatusOk = 200u;
165 
166     const Field& field = GetParam().mField;
167 
168     const bool isUniform = GetParam().mStorageClass == StorageClass::Uniform;
169 
170     std::string shader = R"(
171 struct Data {
172     header : u32;
173     [[align({field_align}), size({field_size})]] field : {field_type};
174     footer : u32;
175 };
176 
177 [[block]] struct Input {
178     header : u32;
179     {data_align}data : Data;
180     {footer_align}footer : u32;
181 };
182 
183 [[block]] struct Output {
184     data : {field_type};
185 };
186 
187 [[block]] struct Status {
188     code : u32;
189 };
190 
191 [[group(0), binding(0)]] var<{input_qualifiers}> input : Input;
192 [[group(0), binding(1)]] var<storage, read_write> output : Output;
193 [[group(0), binding(2)]] var<storage, read_write> status : Status;
194 
195 [[stage(compute), workgroup_size(1,1,1)]]
196 fn main() {
197     if (input.header != {input_header_code}u) {
198         status.code = {status_bad_input_header}u;
199     } elseif (input.footer != {input_footer_code}u) {
200         status.code = {status_bad_input_footer}u;
201     } elseif (input.data.header != {data_header_code}u) {
202         status.code = {status_bad_data_header}u;
203     } elseif (input.data.footer != {data_footer_code}u) {
204         status.code = {status_bad_data_footer}u;
205     } else {
206         status.code = {status_ok}u;
207         output.data = input.data.field;
208     }
209 })";
210 
211     // https://www.w3.org/TR/WGSL/#alignment-and-size
212     // Structure size: roundUp(AlignOf(S), OffsetOf(S, L) + SizeOf(S, L))
213     // https://www.w3.org/TR/WGSL/#storage-class-constraints
214     // RequiredAlignOf(S, uniform): roundUp(16, max(AlignOf(T0), ..., AlignOf(TN)))
215     uint32_t dataAlign = isUniform ? std::max(16u, field.align) : field.align;
216 
217     // https://www.w3.org/TR/WGSL/#structure-layout-rules
218     // Note: When underlying the target is a Vulkan device, we assume the device does not support
219     // the scalarBlockLayout feature. Therefore, a data value must not be placed in the padding at
220     // the end of a structure or matrix, nor in the padding at the last element of an array.
221     uint32_t footerAlign = isUniform ? 16 : 4;
222 
223     shader = ReplaceAll(shader, "{data_align}", isUniform ? AlignDeco(dataAlign) : "");
224     shader = ReplaceAll(shader, "{field_align}", std::to_string(field.align));
225     shader = ReplaceAll(shader, "{footer_align}", isUniform ? AlignDeco(footerAlign) : "");
226     shader = ReplaceAll(shader, "{field_size}",
227                         std::to_string(field.padded_size > 0 ? field.padded_size : field.size));
228     shader = ReplaceAll(shader, "{field_type}", field.type);
229     shader = ReplaceAll(shader, "{input_header_code}", std::to_string(kInputHeaderCode));
230     shader = ReplaceAll(shader, "{input_footer_code}", std::to_string(kInputFooterCode));
231     shader = ReplaceAll(shader, "{data_header_code}", std::to_string(kDataHeaderCode));
232     shader = ReplaceAll(shader, "{data_footer_code}", std::to_string(kDataFooterCode));
233     shader = ReplaceAll(shader, "{status_bad_input_header}", std::to_string(kStatusBadInputHeader));
234     shader = ReplaceAll(shader, "{status_bad_input_footer}", std::to_string(kStatusBadInputFooter));
235     shader = ReplaceAll(shader, "{status_bad_data_header}", std::to_string(kStatusBadDataHeader));
236     shader = ReplaceAll(shader, "{status_bad_data_footer}", std::to_string(kStatusBadDataFooter));
237     shader = ReplaceAll(shader, "{status_ok}", std::to_string(kStatusOk));
238     shader = ReplaceAll(shader, "{input_qualifiers}",
239                         isUniform ? "uniform"  //
240                                   : "storage, read_write");
241 
242     // Set up shader and pipeline
243     auto module = utils::CreateShaderModule(device, shader.c_str());
244 
245     wgpu::ComputePipelineDescriptor csDesc;
246     csDesc.compute.module = module;
247     csDesc.compute.entryPoint = "main";
248 
249     wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
250 
251     // Build the input and expected data.
252     std::vector<uint8_t> inputData;     // The whole SSBO data
253     std::vector<uint8_t> expectedData;  // The expected data to be copied by the shader
254     {
255         auto PushU32 = [&inputData](uint32_t u32) {
256             inputData.emplace_back((u32 >> 0) & 0xff);
257             inputData.emplace_back((u32 >> 8) & 0xff);
258             inputData.emplace_back((u32 >> 16) & 0xff);
259             inputData.emplace_back((u32 >> 24) & 0xff);
260         };
261         auto AlignTo = [&inputData](uint32_t alignment, uint8_t code) {
262             uint32_t target = Align(inputData.size(), alignment);
263             uint32_t bytes = target - inputData.size();
264             for (uint32_t i = 0; i < bytes; i++) {
265                 inputData.emplace_back(code);
266             }
267         };
268         PushU32(kInputHeaderCode);                  // Input.header
269         AlignTo(dataAlign, kDataAlignPaddingCode);  // Input.data
270         {
271             PushU32(kDataHeaderCode);                      // Input.data.header
272             AlignTo(field.align, kFieldAlignPaddingCode);  // Input.data.field
273             for (uint32_t i = 0; i < field.size; i++) {
274                 // The data has the MSB cleared to distinguish it from the
275                 // padding codes.
276                 uint8_t code = i & 0x7f;
277                 inputData.emplace_back(code);  // Input.data.field
278                 expectedData.emplace_back(code);
279             }
280             for (uint32_t i = field.size; i < field.padded_size; i++) {
281                 inputData.emplace_back(kFieldSizePaddingCode);  // Input.data.field padding
282             }
283             PushU32(kDataFooterCode);                    // Input.data.footer
284             AlignTo(field.align, kDataSizePaddingCode);  // Input.data padding
285         }
286         AlignTo(footerAlign, kInputFooterAlignPaddingCode);  // Input.footer [[align]]
287         PushU32(kInputFooterCode);                           // Input.footer
288         AlignTo(256, kInputTailPaddingCode);                 // Input padding
289     }
290 
291     // Set up input storage buffer
292     wgpu::Buffer inputBuf = utils::CreateBufferFromData(
293         device, inputData.data(), inputData.size(),
294         wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst |
295             (isUniform ? wgpu::BufferUsage::Uniform : wgpu::BufferUsage::Storage));
296 
297     // Set up output storage buffer
298     wgpu::BufferDescriptor outputDesc;
299     outputDesc.size = field.size;
300     outputDesc.usage =
301         wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
302     wgpu::Buffer outputBuf = device.CreateBuffer(&outputDesc);
303 
304     // Set up status storage buffer
305     wgpu::BufferDescriptor statusDesc;
306     statusDesc.size = 4u;
307     statusDesc.usage =
308         wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
309     wgpu::Buffer statusBuf = device.CreateBuffer(&statusDesc);
310 
311     // Set up bind group and issue dispatch
312     wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
313                                                      {
314                                                          {0, inputBuf},
315                                                          {1, outputBuf},
316                                                          {2, statusBuf},
317                                                      });
318 
319     wgpu::CommandBuffer commands;
320     {
321         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
322         wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
323         pass.SetPipeline(pipeline);
324         pass.SetBindGroup(0, bindGroup);
325         pass.Dispatch(1);
326         pass.EndPass();
327 
328         commands = encoder.Finish();
329     }
330 
331     queue.Submit(1, &commands);
332 
333     // Check the status
334     EXPECT_BUFFER_U32_EQ(kStatusOk, statusBuf, 0) << "status code error" << std::endl
335                                                   << "Shader: " << shader;
336 
337     // Check the data
338     field.matcher(field.size, [&](uint32_t offset, uint32_t size) {
339         EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size)
340             << "offset: " << offset;
341     });
342 }
343 
344 namespace {
345 
GenerateParams()346     auto GenerateParams() {
347         auto params = MakeParamGenerator<ComputeLayoutMemoryBufferTestParams>(
348             {
349                 D3D12Backend(), MetalBackend(), VulkanBackend(),
350                 // TODO(crbug.com/dawn/942)
351                 // There was a compiler error: Buffer block cannot be expressed as any of std430,
352                 // std140, scalar, even with enhanced layouts. You can try flattening this block to
353                 // support a more flexible layout.
354                 // OpenGLBackend(),
355                 // OpenGLESBackend(),
356             },
357             {StorageClass::Storage, StorageClass::Uniform},
358             {
359                 // See https://www.w3.org/TR/WGSL/#alignment-and-size
360                 // Scalar types with no custom alignment or size
361                 Field{"i32", /* align */ 4, /* size */ 4},
362                 Field{"u32", /* align */ 4, /* size */ 4},
363                 Field{"f32", /* align */ 4, /* size */ 4},
364 
365                 // Scalar types with custom alignment
366                 Field{"i32", /* align */ 16, /* size */ 4},
367                 Field{"u32", /* align */ 16, /* size */ 4},
368                 Field{"f32", /* align */ 16, /* size */ 4},
369 
370                 // Scalar types with custom size
371                 Field{"i32", /* align */ 4, /* size */ 4}.PaddedSize(24),
372                 Field{"u32", /* align */ 4, /* size */ 4}.PaddedSize(24),
373                 Field{"f32", /* align */ 4, /* size */ 4}.PaddedSize(24),
374 
375                 // Vector types with no custom alignment or size
376                 Field{"vec2<i32>", /* align */ 8, /* size */ 8},
377                 Field{"vec2<u32>", /* align */ 8, /* size */ 8},
378                 Field{"vec2<f32>", /* align */ 8, /* size */ 8},
379                 Field{"vec3<i32>", /* align */ 16, /* size */ 12},
380                 Field{"vec3<u32>", /* align */ 16, /* size */ 12},
381                 Field{"vec3<f32>", /* align */ 16, /* size */ 12},
382                 Field{"vec4<i32>", /* align */ 16, /* size */ 16},
383                 Field{"vec4<u32>", /* align */ 16, /* size */ 16},
384                 Field{"vec4<f32>", /* align */ 16, /* size */ 16},
385 
386                 // Vector types with custom alignment
387                 Field{"vec2<i32>", /* align */ 32, /* size */ 8},
388                 Field{"vec2<u32>", /* align */ 32, /* size */ 8},
389                 Field{"vec2<f32>", /* align */ 32, /* size */ 8},
390                 Field{"vec3<i32>", /* align */ 32, /* size */ 12},
391                 Field{"vec3<u32>", /* align */ 32, /* size */ 12},
392                 Field{"vec3<f32>", /* align */ 32, /* size */ 12},
393                 Field{"vec4<i32>", /* align */ 32, /* size */ 16},
394                 Field{"vec4<u32>", /* align */ 32, /* size */ 16},
395                 Field{"vec4<f32>", /* align */ 32, /* size */ 16},
396 
397                 // Vector types with custom size
398                 Field{"vec2<i32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
399                 Field{"vec2<u32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
400                 Field{"vec2<f32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
401                 Field{"vec3<i32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
402                 Field{"vec3<u32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
403                 Field{"vec3<f32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
404                 Field{"vec4<i32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
405                 Field{"vec4<u32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
406                 Field{"vec4<f32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
407 
408                 // Matrix types with no custom alignment or size
409                 Field{"mat2x2<f32>", /* align */ 8, /* size */ 16},
410                 Field{"mat3x2<f32>", /* align */ 8, /* size */ 24},
411                 Field{"mat4x2<f32>", /* align */ 8, /* size */ 32},
412                 Field{"mat2x3<f32>", /* align */ 16, /* size */ 32}.Strided<12, 4>(),
413                 Field{"mat3x3<f32>", /* align */ 16, /* size */ 48}.Strided<12, 4>(),
414                 Field{"mat4x3<f32>", /* align */ 16, /* size */ 64}.Strided<12, 4>(),
415                 Field{"mat2x4<f32>", /* align */ 16, /* size */ 32},
416                 Field{"mat3x4<f32>", /* align */ 16, /* size */ 48},
417                 Field{"mat4x4<f32>", /* align */ 16, /* size */ 64},
418 
419                 // Matrix types with custom alignment
420                 Field{"mat2x2<f32>", /* align */ 32, /* size */ 16},
421                 Field{"mat3x2<f32>", /* align */ 32, /* size */ 24},
422                 Field{"mat4x2<f32>", /* align */ 32, /* size */ 32},
423                 Field{"mat2x3<f32>", /* align */ 32, /* size */ 32}.Strided<12, 4>(),
424                 Field{"mat3x3<f32>", /* align */ 32, /* size */ 48}.Strided<12, 4>(),
425                 Field{"mat4x3<f32>", /* align */ 32, /* size */ 64}.Strided<12, 4>(),
426                 Field{"mat2x4<f32>", /* align */ 32, /* size */ 32},
427                 Field{"mat3x4<f32>", /* align */ 32, /* size */ 48},
428                 Field{"mat4x4<f32>", /* align */ 32, /* size */ 64},
429 
430                 // Matrix types with custom size
431                 Field{"mat2x2<f32>", /* align */ 8, /* size */ 16}.PaddedSize(128),
432                 Field{"mat3x2<f32>", /* align */ 8, /* size */ 24}.PaddedSize(128),
433                 Field{"mat4x2<f32>", /* align */ 8, /* size */ 32}.PaddedSize(128),
434                 Field{"mat2x3<f32>", /* align */ 16, /* size */ 32}
435                     .PaddedSize(128)
436                     .Strided<12, 4>(),
437                 Field{"mat3x3<f32>", /* align */ 16, /* size */ 48}
438                     .PaddedSize(128)
439                     .Strided<12, 4>(),
440                 Field{"mat4x3<f32>", /* align */ 16, /* size */ 64}
441                     .PaddedSize(128)
442                     .Strided<12, 4>(),
443                 Field{"mat2x4<f32>", /* align */ 16, /* size */ 32}.PaddedSize(128),
444                 Field{"mat3x4<f32>", /* align */ 16, /* size */ 48}.PaddedSize(128),
445                 Field{"mat4x4<f32>", /* align */ 16, /* size */ 64}.PaddedSize(128),
446 
447                 // Array types with no custom alignment, size or stride
448                 // Note: The use of StorageBufferOnly() is due to UBOs requiring 16 byte alignment
449                 // of array elements. See https://www.w3.org/TR/WGSL/#storage-class-constraints
450                 Field{"array<u32, 1>", /* align */ 4, /* size */ 4}.StorageBufferOnly(),
451                 Field{"array<u32, 2>", /* align */ 4, /* size */ 8}.StorageBufferOnly(),
452                 Field{"array<u32, 3>", /* align */ 4, /* size */ 12}.StorageBufferOnly(),
453                 Field{"array<u32, 4>", /* align */ 4, /* size */ 16}.StorageBufferOnly(),
454                 Field{"[[stride(16)]] array<u32, 1>", /* align */ 4, /* size */ 16}
455                     .StorageBufferOnly()
456                     .Strided<4, 12>(),
457                 Field{"[[stride(16)]] array<u32, 2>", /* align */ 4, /* size */ 32}
458                     .StorageBufferOnly()
459                     .Strided<4, 12>(),
460                 Field{"[[stride(16)]] array<u32, 3>", /* align */ 4, /* size */ 48}
461                     .StorageBufferOnly()
462                     .Strided<4, 12>(),
463                 Field{"[[stride(16)]] array<u32, 4>", /* align */ 4, /* size */ 64}
464                     .StorageBufferOnly()
465                     .Strided<4, 12>(),
466                 Field{"array<vec3<u32>, 4>", /* align */ 16, /* size */ 64}.Strided<12, 4>(),
467                 Field{"[[stride(32)]] array<vec3<u32>, 4>", /* align */ 16, /* size */ 128}
468                     .Strided<12, 20>(),
469 
470                 // Array types with custom alignment
471                 Field{"array<u32, 1>", /* align */ 32, /* size */ 4}.StorageBufferOnly(),
472                 Field{"array<u32, 2>", /* align */ 32, /* size */ 8}.StorageBufferOnly(),
473                 Field{"array<u32, 3>", /* align */ 32, /* size */ 12}.StorageBufferOnly(),
474                 Field{"array<u32, 4>", /* align */ 32, /* size */ 16}.StorageBufferOnly(),
475                 Field{"[[stride(16)]] array<u32, 1>", /* align */ 32, /* size */ 16}
476                     .Strided<4, 12>(),
477                 Field{"[[stride(16)]] array<u32, 2>", /* align */ 32, /* size */ 32}
478                     .Strided<4, 12>(),
479                 Field{"[[stride(16)]] array<u32, 3>", /* align */ 32, /* size */ 48}
480                     .Strided<4, 12>(),
481                 Field{"[[stride(16)]] array<u32, 4>", /* align */ 32, /* size */ 64}
482                     .Strided<4, 12>(),
483                 Field{"array<vec3<u32>, 4>", /* align */ 32, /* size */ 64}.Strided<12, 4>(),
484 
485                 // Array types with custom size
486                 Field{"array<u32, 1>", /* align */ 4, /* size */ 4}
487                     .PaddedSize(128)
488                     .StorageBufferOnly(),
489                 Field{"array<u32, 2>", /* align */ 4, /* size */ 8}
490                     .PaddedSize(128)
491                     .StorageBufferOnly(),
492                 Field{"array<u32, 3>", /* align */ 4, /* size */ 12}
493                     .PaddedSize(128)
494                     .StorageBufferOnly(),
495                 Field{"array<u32, 4>", /* align */ 4, /* size */ 16}
496                     .PaddedSize(128)
497                     .StorageBufferOnly(),
498                 Field{"array<vec3<u32>, 4>", /* align */ 16, /* size */ 64}
499                     .PaddedSize(128)
500                     .Strided<12, 4>(),
501             });
502 
503         std::vector<ComputeLayoutMemoryBufferTestParams> filtered;
504         for (auto param : params) {
505             if (param.mStorageClass != StorageClass::Storage && param.mField.storage_buffer_only) {
506                 continue;
507             }
508             filtered.emplace_back(param);
509         }
510         return filtered;
511     }
512 
513     INSTANTIATE_TEST_SUITE_P(
514         ,
515         ComputeLayoutMemoryBufferTests,
516         ::testing::ValuesIn(GenerateParams()),
517         DawnTestBase::PrintToStringParamName("ComputeLayoutMemoryBufferTests"));
518     GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ComputeLayoutMemoryBufferTests);
519 
520 }  // namespace
521