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