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