• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 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/unittests/validation/ValidationTest.h"
16 
17 #include "common/Assert.h"
18 #include "common/Constants.h"
19 #include "utils/ComboRenderPipelineDescriptor.h"
20 #include "utils/WGPUHelpers.h"
21 
22 namespace {
23     // Helper for describing bindings throughout the tests
24     struct BindingDescriptor {
25         uint32_t group;
26         uint32_t binding;
27         std::string decl;
28         std::string ref_type;
29         std::string ref_mem;
30         uint64_t size;
31         wgpu::BufferBindingType type = wgpu::BufferBindingType::Storage;
32         wgpu::ShaderStage visibility = wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment;
33     };
34 
35     // Runs |func| with a modified version of |originalSizes| as an argument, adding |offset| to
36     // each element one at a time This is useful to verify some behavior happens if any element is
37     // offset from original
38     template <typename F>
WithEachSizeOffsetBy(int64_t offset,const std::vector<uint64_t> & originalSizes,F func)39     void WithEachSizeOffsetBy(int64_t offset, const std::vector<uint64_t>& originalSizes, F func) {
40         std::vector<uint64_t> modifiedSizes = originalSizes;
41         for (size_t i = 0; i < originalSizes.size(); ++i) {
42             if (offset < 0) {
43                 ASSERT(originalSizes[i] >= static_cast<uint64_t>(-offset));
44             }
45             // Run the function with an element offset, and restore element afterwards
46             modifiedSizes[i] += offset;
47             func(modifiedSizes);
48             modifiedSizes[i] -= offset;
49         }
50     }
51 
52     // Runs |func| with |correctSizes|, and an expectation of success and failure
53     template <typename F>
CheckSizeBounds(const std::vector<uint64_t> & correctSizes,F func)54     void CheckSizeBounds(const std::vector<uint64_t>& correctSizes, F func) {
55         // To validate size:
56         // Check invalid with bind group with one less
57         // Check valid with bind group with correct size
58 
59         // Make sure (every size - 1) produces an error
60         WithEachSizeOffsetBy(-1, correctSizes,
61                              [&](const std::vector<uint64_t>& sizes) { func(sizes, false); });
62 
63         // Make sure correct sizes work
64         func(correctSizes, true);
65 
66         // Make sure (every size + 1) works
67         WithEachSizeOffsetBy(1, correctSizes,
68                              [&](const std::vector<uint64_t>& sizes) { func(sizes, true); });
69     }
70 
71     // Creates a bind group with given bindings for shader text
GenerateBindingString(const std::vector<BindingDescriptor> & bindings)72     std::string GenerateBindingString(const std::vector<BindingDescriptor>& bindings) {
73         std::ostringstream ostream;
74         size_t index = 0;
75         for (const BindingDescriptor& b : bindings) {
76             ostream << "[[block]] struct S" << index << " { " << b.decl << "};\n";
77             ostream << "[[group(" << b.group << "), binding(" << b.binding << ")]] ";
78             switch (b.type) {
79                 case wgpu::BufferBindingType::Uniform:
80                     ostream << "var<uniform> b" << index << " : S" << index << ";\n";
81                     break;
82                 case wgpu::BufferBindingType::Storage:
83                     ostream << "var<storage, read_write> b" << index << " : S" << index << ";\n";
84                     break;
85                 case wgpu::BufferBindingType::ReadOnlyStorage:
86                     ostream << "var<storage, read> b" << index << " : S" << index << ";\n";
87                     break;
88                 default:
89                     UNREACHABLE();
90             }
91             index++;
92         }
93         return ostream.str();
94     }
95 
GenerateReferenceString(const std::vector<BindingDescriptor> & bindings,wgpu::ShaderStage stage)96     std::string GenerateReferenceString(const std::vector<BindingDescriptor>& bindings,
97                                         wgpu::ShaderStage stage) {
98         std::ostringstream ostream;
99         size_t index = 0;
100         for (const BindingDescriptor& b : bindings) {
101             if (b.visibility & stage) {
102                 if (!b.ref_type.empty() && !b.ref_mem.empty()) {
103                     ostream << "var r" << index << " : " << b.ref_type << " = b" << index << "."
104                             << b.ref_mem << ";\n";
105                 }
106             }
107             index++;
108         }
109         return ostream.str();
110     }
111 
112     // Used for adding custom types available throughout the tests
113     static const std::string kStructs = "struct ThreeFloats {f1 : f32; f2 : f32; f3 : f32;};\n";
114 
115     // Creates a compute shader with given bindings
CreateComputeShaderWithBindings(const std::vector<BindingDescriptor> & bindings)116     std::string CreateComputeShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
117         return kStructs + GenerateBindingString(bindings) +
118                "[[stage(compute), workgroup_size(1,1,1)]] fn main() {\n" +
119                GenerateReferenceString(bindings, wgpu::ShaderStage::Compute) + "}";
120     }
121 
122     // Creates a vertex shader with given bindings
CreateVertexShaderWithBindings(const std::vector<BindingDescriptor> & bindings)123     std::string CreateVertexShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
124         return kStructs + GenerateBindingString(bindings) +
125                "[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {\n" +
126                GenerateReferenceString(bindings, wgpu::ShaderStage::Vertex) +
127                "\n   return vec4<f32>(); " + "}";
128     }
129 
130     // Creates a fragment shader with given bindings
CreateFragmentShaderWithBindings(const std::vector<BindingDescriptor> & bindings)131     std::string CreateFragmentShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
132         return kStructs + GenerateBindingString(bindings) + "[[stage(fragment)]] fn main() {\n" +
133                GenerateReferenceString(bindings, wgpu::ShaderStage::Fragment) + "}";
134     }
135 
136     // Concatenates vectors containing BindingDescriptor
CombineBindings(std::initializer_list<std::vector<BindingDescriptor>> bindings)137     std::vector<BindingDescriptor> CombineBindings(
138         std::initializer_list<std::vector<BindingDescriptor>> bindings) {
139         std::vector<BindingDescriptor> result;
140         for (const std::vector<BindingDescriptor>& b : bindings) {
141             result.insert(result.end(), b.begin(), b.end());
142         }
143         return result;
144     }
145 }  // namespace
146 
147 class MinBufferSizeTestsBase : public ValidationTest {
148   public:
SetUp()149     void SetUp() override {
150         ValidationTest::SetUp();
151     }
152 
CreateBuffer(uint64_t bufferSize,wgpu::BufferUsage usage)153     wgpu::Buffer CreateBuffer(uint64_t bufferSize, wgpu::BufferUsage usage) {
154         wgpu::BufferDescriptor bufferDescriptor;
155         bufferDescriptor.size = bufferSize;
156         bufferDescriptor.usage = usage;
157 
158         return device.CreateBuffer(&bufferDescriptor);
159     }
160 
161     // Creates compute pipeline given a layout and shader
CreateComputePipeline(const std::vector<wgpu::BindGroupLayout> & layouts,const std::string & shader)162     wgpu::ComputePipeline CreateComputePipeline(const std::vector<wgpu::BindGroupLayout>& layouts,
163                                                 const std::string& shader) {
164         wgpu::ShaderModule csModule = utils::CreateShaderModule(device, shader.c_str());
165 
166         wgpu::ComputePipelineDescriptor csDesc;
167         csDesc.layout = nullptr;
168         if (!layouts.empty()) {
169             wgpu::PipelineLayoutDescriptor descriptor;
170             descriptor.bindGroupLayoutCount = layouts.size();
171             descriptor.bindGroupLayouts = layouts.data();
172             csDesc.layout = device.CreatePipelineLayout(&descriptor);
173         }
174         csDesc.compute.module = csModule;
175         csDesc.compute.entryPoint = "main";
176 
177         return device.CreateComputePipeline(&csDesc);
178     }
179 
180     // Creates compute pipeline with default layout
CreateComputePipelineWithDefaultLayout(const std::string & shader)181     wgpu::ComputePipeline CreateComputePipelineWithDefaultLayout(const std::string& shader) {
182         return CreateComputePipeline({}, shader);
183     }
184 
185     // Creates render pipeline give na layout and shaders
CreateRenderPipeline(const std::vector<wgpu::BindGroupLayout> & layouts,const std::string & vertexShader,const std::string & fragShader)186     wgpu::RenderPipeline CreateRenderPipeline(const std::vector<wgpu::BindGroupLayout>& layouts,
187                                               const std::string& vertexShader,
188                                               const std::string& fragShader) {
189         wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str());
190 
191         wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragShader.c_str());
192 
193         utils::ComboRenderPipelineDescriptor pipelineDescriptor;
194         pipelineDescriptor.vertex.module = vsModule;
195         pipelineDescriptor.cFragment.module = fsModule;
196         pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
197         pipelineDescriptor.layout = nullptr;
198         if (!layouts.empty()) {
199             wgpu::PipelineLayoutDescriptor descriptor;
200             descriptor.bindGroupLayoutCount = layouts.size();
201             descriptor.bindGroupLayouts = layouts.data();
202             pipelineDescriptor.layout = device.CreatePipelineLayout(&descriptor);
203         }
204 
205         return device.CreateRenderPipeline(&pipelineDescriptor);
206     }
207 
208     // Creates render pipeline with default layout
CreateRenderPipelineWithDefaultLayout(const std::string & vertexShader,const std::string & fragShader)209     wgpu::RenderPipeline CreateRenderPipelineWithDefaultLayout(const std::string& vertexShader,
210                                                                const std::string& fragShader) {
211         return CreateRenderPipeline({}, vertexShader, fragShader);
212     }
213 
214     // Creates bind group layout with given minimum sizes for each binding
CreateBindGroupLayout(const std::vector<BindingDescriptor> & bindings,const std::vector<uint64_t> & minimumSizes)215     wgpu::BindGroupLayout CreateBindGroupLayout(const std::vector<BindingDescriptor>& bindings,
216                                                 const std::vector<uint64_t>& minimumSizes) {
217         ASSERT(bindings.size() == minimumSizes.size());
218         std::vector<wgpu::BindGroupLayoutEntry> entries;
219 
220         for (size_t i = 0; i < bindings.size(); ++i) {
221             const BindingDescriptor& b = bindings[i];
222             wgpu::BindGroupLayoutEntry e = {};
223             e.binding = b.binding;
224             e.visibility = b.visibility;
225             e.buffer.type = b.type;
226             e.buffer.minBindingSize = minimumSizes[i];
227             entries.push_back(e);
228         }
229 
230         wgpu::BindGroupLayoutDescriptor descriptor;
231         descriptor.entryCount = static_cast<uint32_t>(entries.size());
232         descriptor.entries = entries.data();
233         return device.CreateBindGroupLayout(&descriptor);
234     }
235 
236     // Extract the first bind group from a compute shader
GetBGLFromComputeShader(const std::string & shader,uint32_t index)237     wgpu::BindGroupLayout GetBGLFromComputeShader(const std::string& shader, uint32_t index) {
238         wgpu::ComputePipeline pipeline = CreateComputePipelineWithDefaultLayout(shader);
239         return pipeline.GetBindGroupLayout(index);
240     }
241 
242     // Extract the first bind group from a render pass
GetBGLFromRenderShaders(const std::string & vertexShader,const std::string & fragShader,uint32_t index)243     wgpu::BindGroupLayout GetBGLFromRenderShaders(const std::string& vertexShader,
244                                                   const std::string& fragShader,
245                                                   uint32_t index) {
246         wgpu::RenderPipeline pipeline =
247             CreateRenderPipelineWithDefaultLayout(vertexShader, fragShader);
248         return pipeline.GetBindGroupLayout(index);
249     }
250 
251     // Create a bind group with given binding sizes for each entry (backed by the same buffer)
CreateBindGroup(wgpu::BindGroupLayout layout,const std::vector<BindingDescriptor> & bindings,const std::vector<uint64_t> & bindingSizes)252     wgpu::BindGroup CreateBindGroup(wgpu::BindGroupLayout layout,
253                                     const std::vector<BindingDescriptor>& bindings,
254                                     const std::vector<uint64_t>& bindingSizes) {
255         ASSERT(bindings.size() == bindingSizes.size());
256         wgpu::Buffer buffer =
257             CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage);
258 
259         std::vector<wgpu::BindGroupEntry> entries;
260         entries.reserve(bindingSizes.size());
261 
262         for (uint32_t i = 0; i < bindingSizes.size(); ++i) {
263             wgpu::BindGroupEntry entry = {};
264             entry.binding = bindings[i].binding;
265             entry.buffer = buffer;
266             ASSERT(bindingSizes[i] < 1024);
267             entry.size = bindingSizes[i];
268             entries.push_back(entry);
269         }
270 
271         wgpu::BindGroupDescriptor descriptor;
272         descriptor.layout = layout;
273         descriptor.entryCount = entries.size();
274         descriptor.entries = entries.data();
275 
276         return device.CreateBindGroup(&descriptor);
277     }
278 
279     // Runs a single dispatch with given pipeline and bind group (to test lazy validation during
280     // dispatch)
TestDispatch(const wgpu::ComputePipeline & computePipeline,const std::vector<wgpu::BindGroup> & bindGroups,bool expectation)281     void TestDispatch(const wgpu::ComputePipeline& computePipeline,
282                       const std::vector<wgpu::BindGroup>& bindGroups,
283                       bool expectation) {
284         wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
285         wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
286         computePassEncoder.SetPipeline(computePipeline);
287         for (size_t i = 0; i < bindGroups.size(); ++i) {
288             computePassEncoder.SetBindGroup(i, bindGroups[i]);
289         }
290         computePassEncoder.Dispatch(1);
291         computePassEncoder.EndPass();
292         if (!expectation) {
293             ASSERT_DEVICE_ERROR(commandEncoder.Finish());
294         } else {
295             commandEncoder.Finish();
296         }
297     }
298 
299     // Runs a single draw with given pipeline and bind group (to test lazy validation during draw)
TestDraw(const wgpu::RenderPipeline & renderPipeline,const std::vector<wgpu::BindGroup> & bindGroups,bool expectation)300     void TestDraw(const wgpu::RenderPipeline& renderPipeline,
301                   const std::vector<wgpu::BindGroup>& bindGroups,
302                   bool expectation) {
303         DummyRenderPass renderPass(device);
304 
305         wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
306         wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
307         renderPassEncoder.SetPipeline(renderPipeline);
308         for (size_t i = 0; i < bindGroups.size(); ++i) {
309             renderPassEncoder.SetBindGroup(i, bindGroups[i]);
310         }
311         renderPassEncoder.Draw(3);
312         renderPassEncoder.EndPass();
313         if (!expectation) {
314             ASSERT_DEVICE_ERROR(commandEncoder.Finish());
315         } else {
316             commandEncoder.Finish();
317         }
318     }
319 };
320 
321 // The check between BGL and pipeline at pipeline creation time
322 class MinBufferSizePipelineCreationTests : public MinBufferSizeTestsBase {};
323 
324 // Pipeline can be created if minimum buffer size in layout is specified as 0
TEST_F(MinBufferSizePipelineCreationTests,ZeroMinBufferSize)325 TEST_F(MinBufferSizePipelineCreationTests, ZeroMinBufferSize) {
326     std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
327                                                {0, 1, "c : f32;", "f32", "c", 4}};
328 
329     std::string computeShader = CreateComputeShaderWithBindings(bindings);
330     std::string vertexShader = CreateVertexShaderWithBindings({});
331     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
332 
333     wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0});
334     CreateRenderPipeline({layout}, vertexShader, fragShader);
335     CreateComputePipeline({layout}, computeShader);
336 }
337 
338 // Fail if layout given has non-zero minimum sizes smaller than shader requirements
TEST_F(MinBufferSizePipelineCreationTests,LayoutSizesTooSmall)339 TEST_F(MinBufferSizePipelineCreationTests, LayoutSizesTooSmall) {
340     std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
341                                                {0, 1, "c : f32;", "f32", "c", 4}};
342 
343     std::string computeShader = CreateComputeShaderWithBindings(bindings);
344     std::string vertexShader = CreateVertexShaderWithBindings({});
345     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
346 
347     CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
348         wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, sizes);
349         if (expectation) {
350             CreateRenderPipeline({layout}, vertexShader, fragShader);
351             CreateComputePipeline({layout}, computeShader);
352         } else {
353             ASSERT_DEVICE_ERROR(CreateRenderPipeline({layout}, vertexShader, fragShader));
354             ASSERT_DEVICE_ERROR(CreateComputePipeline({layout}, computeShader));
355         }
356     });
357 }
358 
359 // Fail if layout given has non-zero minimum sizes smaller than shader requirements
TEST_F(MinBufferSizePipelineCreationTests,LayoutSizesTooSmallMultipleGroups)360 TEST_F(MinBufferSizePipelineCreationTests, LayoutSizesTooSmallMultipleGroups) {
361     std::vector<BindingDescriptor> bg0Bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
362                                                   {0, 1, "c : f32;", "f32", "c", 4}};
363     std::vector<BindingDescriptor> bg1Bindings = {
364         {1, 0, "d : f32; e : f32; f : f32;", "f32", "e", 12},
365         {1, 1, "g : mat2x2<f32>;", "mat2x2<f32>", "g", 16}};
366     std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
367 
368     std::string computeShader = CreateComputeShaderWithBindings(bindings);
369     std::string vertexShader = CreateVertexShaderWithBindings({});
370     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
371 
372     CheckSizeBounds({8, 4, 12, 16}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
373         wgpu::BindGroupLayout layout0 = CreateBindGroupLayout(bg0Bindings, {sizes[0], sizes[1]});
374         wgpu::BindGroupLayout layout1 = CreateBindGroupLayout(bg1Bindings, {sizes[2], sizes[3]});
375         if (expectation) {
376             CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader);
377             CreateComputePipeline({layout0, layout1}, computeShader);
378         } else {
379             ASSERT_DEVICE_ERROR(CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader));
380             ASSERT_DEVICE_ERROR(CreateComputePipeline({layout0, layout1}, computeShader));
381         }
382     });
383 }
384 
385 // The check between the BGL and the bindings at bindgroup creation time
386 class MinBufferSizeBindGroupCreationTests : public MinBufferSizeTestsBase {};
387 
388 // Fail if a binding is smaller than minimum buffer size
TEST_F(MinBufferSizeBindGroupCreationTests,BindingTooSmall)389 TEST_F(MinBufferSizeBindGroupCreationTests, BindingTooSmall) {
390     std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
391                                                {0, 1, "c : f32;", "f32", "c", 4}};
392     wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {8, 4});
393 
394     CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
395         if (expectation) {
396             CreateBindGroup(layout, bindings, sizes);
397         } else {
398             ASSERT_DEVICE_ERROR(CreateBindGroup(layout, bindings, sizes));
399         }
400     });
401 }
402 
403 // Check two layouts with different minimum size are unequal
TEST_F(MinBufferSizeBindGroupCreationTests,LayoutEquality)404 TEST_F(MinBufferSizeBindGroupCreationTests, LayoutEquality) {
405     // Returning the same pointer is an implementation detail of Dawn Native.
406     // It is not the same semantic with the Wire.
407     DAWN_SKIP_TEST_IF(UsesWire());
408 
409     auto MakeLayout = [&](uint64_t size) {
410         return utils::MakeBindGroupLayout(
411             device,
412             {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform, false, size}});
413     };
414 
415     EXPECT_EQ(MakeLayout(0).Get(), MakeLayout(0).Get());
416     EXPECT_NE(MakeLayout(0).Get(), MakeLayout(4).Get());
417 }
418 
419 // The check between the bindgroup binding sizes and the required pipeline sizes at draw time
420 class MinBufferSizeDrawTimeValidationTests : public MinBufferSizeTestsBase {};
421 
422 // Fail if binding sizes are too small at draw time
TEST_F(MinBufferSizeDrawTimeValidationTests,ZeroMinSizeAndTooSmallBinding)423 TEST_F(MinBufferSizeDrawTimeValidationTests, ZeroMinSizeAndTooSmallBinding) {
424     std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
425                                                {0, 1, "c : f32;", "f32", "c", 4}};
426 
427     std::string computeShader = CreateComputeShaderWithBindings(bindings);
428     std::string vertexShader = CreateVertexShaderWithBindings({});
429     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
430 
431     wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0});
432 
433     wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader);
434     wgpu::RenderPipeline renderPipeline = CreateRenderPipeline({layout}, vertexShader, fragShader);
435 
436     CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
437         wgpu::BindGroup bindGroup = CreateBindGroup(layout, bindings, sizes);
438         TestDispatch(computePipeline, {bindGroup}, expectation);
439         TestDraw(renderPipeline, {bindGroup}, expectation);
440     });
441 }
442 
443 // Draw time validation works for non-contiguous bindings
TEST_F(MinBufferSizeDrawTimeValidationTests,UnorderedBindings)444 TEST_F(MinBufferSizeDrawTimeValidationTests, UnorderedBindings) {
445     std::vector<BindingDescriptor> bindings = {
446         {0, 2, "a : f32; b : f32;", "f32", "a", 8},
447         {0, 0, "c : f32;", "f32", "c", 4},
448         {0, 4, "d : f32; e : f32; f : f32;", "f32", "e", 12}};
449 
450     std::string computeShader = CreateComputeShaderWithBindings(bindings);
451     std::string vertexShader = CreateVertexShaderWithBindings({});
452     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
453 
454     wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0, 0});
455 
456     wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader);
457     wgpu::RenderPipeline renderPipeline = CreateRenderPipeline({layout}, vertexShader, fragShader);
458 
459     CheckSizeBounds({8, 4, 12}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
460         wgpu::BindGroup bindGroup = CreateBindGroup(layout, bindings, sizes);
461         TestDispatch(computePipeline, {bindGroup}, expectation);
462         TestDraw(renderPipeline, {bindGroup}, expectation);
463     });
464 }
465 
466 // Draw time validation works for multiple bind groups
TEST_F(MinBufferSizeDrawTimeValidationTests,MultipleGroups)467 TEST_F(MinBufferSizeDrawTimeValidationTests, MultipleGroups) {
468     std::vector<BindingDescriptor> bg0Bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
469                                                   {0, 1, "c : f32;", "f32", "c", 4}};
470     std::vector<BindingDescriptor> bg1Bindings = {
471         {1, 0, "d : f32; e : f32; f : f32;", "f32", "e", 12},
472         {1, 1, "g : mat2x2<f32>;", "mat2x2<f32>", "g", 16}};
473     std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
474 
475     std::string computeShader = CreateComputeShaderWithBindings(bindings);
476     std::string vertexShader = CreateVertexShaderWithBindings({});
477     std::string fragShader = CreateFragmentShaderWithBindings(bindings);
478 
479     wgpu::BindGroupLayout layout0 = CreateBindGroupLayout(bg0Bindings, {0, 0});
480     wgpu::BindGroupLayout layout1 = CreateBindGroupLayout(bg1Bindings, {0, 0});
481 
482     wgpu::ComputePipeline computePipeline =
483         CreateComputePipeline({layout0, layout1}, computeShader);
484     wgpu::RenderPipeline renderPipeline =
485         CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader);
486 
487     CheckSizeBounds({8, 4, 12, 16}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
488         wgpu::BindGroup bindGroup0 = CreateBindGroup(layout0, bg0Bindings, {sizes[0], sizes[1]});
489         wgpu::BindGroup bindGroup1 = CreateBindGroup(layout0, bg0Bindings, {sizes[2], sizes[3]});
490         TestDispatch(computePipeline, {bindGroup0, bindGroup1}, expectation);
491         TestDraw(renderPipeline, {bindGroup0, bindGroup1}, expectation);
492     });
493 }
494 
495 // The correctness of minimum buffer size for the defaulted layout for a pipeline
496 class MinBufferSizeDefaultLayoutTests : public MinBufferSizeTestsBase {
497   public:
498     // Checks BGL |layout| has minimum buffer sizes equal to sizes in |bindings|
CheckLayoutBindingSizeValidation(const wgpu::BindGroupLayout & layout,const std::vector<BindingDescriptor> & bindings)499     void CheckLayoutBindingSizeValidation(const wgpu::BindGroupLayout& layout,
500                                           const std::vector<BindingDescriptor>& bindings) {
501         std::vector<uint64_t> correctSizes;
502         correctSizes.reserve(bindings.size());
503         for (const BindingDescriptor& b : bindings) {
504             correctSizes.push_back(b.size);
505         }
506 
507         CheckSizeBounds(correctSizes, [&](const std::vector<uint64_t>& sizes, bool expectation) {
508             if (expectation) {
509                 CreateBindGroup(layout, bindings, sizes);
510             } else {
511                 ASSERT_DEVICE_ERROR(CreateBindGroup(layout, bindings, sizes));
512             }
513         });
514     }
515 
516     // Constructs shaders with given layout type and bindings, checking defaulted sizes match sizes
517     // in |bindings|
CheckShaderBindingSizeReflection(std::initializer_list<std::vector<BindingDescriptor>> bindings)518     void CheckShaderBindingSizeReflection(
519         std::initializer_list<std::vector<BindingDescriptor>> bindings) {
520         std::vector<BindingDescriptor> combinedBindings = CombineBindings(bindings);
521         std::string computeShader = CreateComputeShaderWithBindings(combinedBindings);
522         std::string vertexShader = CreateVertexShaderWithBindings({});
523         std::string fragShader = CreateFragmentShaderWithBindings(combinedBindings);
524 
525         size_t i = 0;
526         for (const std::vector<BindingDescriptor>& b : bindings) {
527             wgpu::BindGroupLayout computeLayout = GetBGLFromComputeShader(computeShader, i);
528             wgpu::BindGroupLayout renderLayout =
529                 GetBGLFromRenderShaders(vertexShader, fragShader, i);
530 
531             CheckLayoutBindingSizeValidation(computeLayout, b);
532             CheckLayoutBindingSizeValidation(renderLayout, b);
533             ++i;
534         }
535     }
536 };
537 
538 // Test the minimum size computations for various WGSL types.
TEST_F(MinBufferSizeDefaultLayoutTests,DefaultLayoutVariousWGSLTypes)539 TEST_F(MinBufferSizeDefaultLayoutTests, DefaultLayoutVariousWGSLTypes) {
540     CheckShaderBindingSizeReflection({{{0, 0, "a : f32;", "f32", "a", 4},
541                                        {0, 1, "b : array<f32>;", "f32", "b[0]", 4},
542                                        {0, 2, "c : mat2x2<f32>;", "mat2x2<f32>", "c", 16}}});
543     CheckShaderBindingSizeReflection({{{0, 3, "d : u32; e : array<f32>;", "u32", "d", 8},
544                                        {0, 4, "f : ThreeFloats;", "f32", "f.f1", 12},
545                                        {0, 5, "g : array<ThreeFloats>;", "f32", "g[0].f1", 12}}});
546 }
547 
548 // Test the minimum size computations for various buffer binding types.
TEST_F(MinBufferSizeDefaultLayoutTests,DefaultLayoutVariousBindingTypes)549 TEST_F(MinBufferSizeDefaultLayoutTests, DefaultLayoutVariousBindingTypes) {
550     CheckShaderBindingSizeReflection(
551         {{{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform},
552           {0, 1, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Storage},
553           {0, 2, "a : f32; b : f32; c: f32;", "f32", "a", 12,
554            wgpu::BufferBindingType::ReadOnlyStorage}}});
555 }
556 
557 // Test the minimum size computations works with multiple bind groups.
TEST_F(MinBufferSizeDefaultLayoutTests,MultipleBindGroups)558 TEST_F(MinBufferSizeDefaultLayoutTests, MultipleBindGroups) {
559     CheckShaderBindingSizeReflection(
560         {{{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform}},
561          {{1, 0, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Storage}},
562          {{2, 0, "a : f32; b : f32; c : f32;", "f32", "a", 12,
563            wgpu::BufferBindingType::ReadOnlyStorage}}});
564 }
565 
566 // Test the minimum size computations with manual size/align/stride decorations.
TEST_F(MinBufferSizeDefaultLayoutTests,NonDefaultLayout)567 TEST_F(MinBufferSizeDefaultLayoutTests, NonDefaultLayout) {
568     CheckShaderBindingSizeReflection(
569         {{{0, 0, "[[size(256)]] a : u32; b : u32;", "u32", "a", 260},
570           {0, 1, "c : u32; [[align(16)]] d : u32;", "u32", "c", 20},
571           {0, 2, "d : [[stride(40)]] array<u32, 3>;", "u32", "d[0]", 120},
572           {0, 3, "e : [[stride(40)]] array<u32>;", "u32", "e[0]", 40}}});
573 }
574 
575 // Minimum size should be the max requirement of both vertex and fragment stages.
TEST_F(MinBufferSizeDefaultLayoutTests,RenderPassConsidersBothStages)576 TEST_F(MinBufferSizeDefaultLayoutTests, RenderPassConsidersBothStages) {
577     std::string vertexShader = CreateVertexShaderWithBindings(
578         {{0, 0, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Uniform,
579           wgpu::ShaderStage::Vertex},
580          {0, 1, "c : vec4<f32>;", "vec4<f32>", "c", 16, wgpu::BufferBindingType::Uniform,
581           wgpu::ShaderStage::Vertex}});
582     std::string fragShader = CreateFragmentShaderWithBindings(
583         {{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform,
584           wgpu::ShaderStage::Fragment},
585          {0, 1, "b : f32; c : f32;", "f32", "b", 8, wgpu::BufferBindingType::Uniform,
586           wgpu::ShaderStage::Fragment}});
587 
588     wgpu::BindGroupLayout renderLayout = GetBGLFromRenderShaders(vertexShader, fragShader, 0);
589 
590     CheckLayoutBindingSizeValidation(renderLayout, {{0, 0, "", "", "", 8}, {0, 1, "", "", "", 16}});
591 }
592