Flutter Impeller
compute_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "flutter/fml/synchronization/waitable_event.h"
6 #include "flutter/testing/testing.h"
7 #include "gmock/gmock.h"
9 #include "impeller/fixtures/sample.comp.h"
10 #include "impeller/fixtures/stage1.comp.h"
11 #include "impeller/fixtures/stage2.comp.h"
16 #include "impeller/renderer/prefix_sum_test.comp.h"
17 #include "impeller/renderer/threadgroup_sizing_test.comp.h"
18 
19 namespace {
20 std::shared_ptr<impeller::HostBuffer> CreateHostBufferFromContext(
21  const std::shared_ptr<impeller::Context>& context) {
23  context->GetResourceAllocator(), context->GetIdleWaiter(),
24  context->GetCapabilities()->GetMinimumUniformAlignment());
25 }
26 } // namespace
27 
28 namespace impeller {
29 namespace testing {
32 
33 TEST_P(ComputeTest, CapabilitiesReportSupport) {
34  auto context = GetContext();
35  ASSERT_TRUE(context);
36  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
37 }
38 
39 TEST_P(ComputeTest, CanCreateComputePass) {
40  using CS = SampleComputeShader;
41  auto context = GetContext();
42  auto host_buffer = CreateHostBufferFromContext(context);
43  ASSERT_TRUE(context);
44  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
45 
46  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
47  auto pipeline_desc =
48  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
49  ASSERT_TRUE(pipeline_desc.has_value());
50  auto compute_pipeline =
51  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
52  ASSERT_TRUE(compute_pipeline);
53 
54  auto cmd_buffer = context->CreateCommandBuffer();
55  auto pass = cmd_buffer->CreateComputePass();
56  ASSERT_TRUE(pass && pass->IsValid());
57 
58  static constexpr size_t kCount = 5;
59 
60  pass->SetPipeline(compute_pipeline);
61 
62  CS::Info info{.count = kCount};
63  CS::Input0<kCount> input_0;
64  CS::Input1<kCount> input_1;
65  for (size_t i = 0; i < kCount; i++) {
66  input_0.elements[i] = Vector4(2.0 + i, 3.0 + i, 4.0 + i, 5.0 * i);
67  input_1.elements[i] = Vector4(6.0, 7.0, 8.0, 9.0);
68  }
69 
70  input_0.fixed_array[1] = IPoint32(2, 2);
71  input_1.fixed_array[0] = UintPoint32(3, 3);
72  input_0.some_int = 5;
73  input_1.some_struct = CS::SomeStruct{.vf = Point(3, 4), .i = 42};
74 
75  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::Output<kCount>>(
76  context, "Output Buffer");
77 
78  CS::BindInfo(*pass, host_buffer->EmplaceUniform(info));
79  CS::BindInput0(*pass, host_buffer->EmplaceStorageBuffer(input_0));
80  CS::BindInput1(*pass, host_buffer->EmplaceStorageBuffer(input_1));
81  CS::BindOutput(*pass, DeviceBuffer::AsBufferView(output_buffer));
82 
83  ASSERT_TRUE(pass->Compute(ISize(kCount, 1)).ok());
84  ASSERT_TRUE(pass->EncodeCommands());
85 
86  fml::AutoResetWaitableEvent latch;
87  ASSERT_TRUE(
88  context->GetCommandQueue()
89  ->Submit(
90  {cmd_buffer},
91  [&latch, output_buffer, &input_0,
92  &input_1](CommandBuffer::Status status) {
93  EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
94 
95  auto view = DeviceBuffer::AsBufferView(output_buffer);
96  EXPECT_EQ(view.GetRange().length, sizeof(CS::Output<kCount>));
97 
98  CS::Output<kCount>* output =
99  reinterpret_cast<CS::Output<kCount>*>(
100  output_buffer->OnGetContents());
101  EXPECT_TRUE(output);
102  for (size_t i = 0; i < kCount; i++) {
103  Vector4 vector = output->elements[i];
104  Vector4 computed = input_0.elements[i] * input_1.elements[i];
105  EXPECT_EQ(vector,
106  Vector4(computed.x + 2 + input_1.some_struct.i,
107  computed.y + 3 + input_1.some_struct.vf.x,
108  computed.z + 5 + input_1.some_struct.vf.y,
109  computed.w));
110  }
111  latch.Signal();
112  })
113  .ok());
114 
115  latch.Wait();
116 }
117 
118 TEST_P(ComputeTest, CanComputePrefixSum) {
119  using CS = PrefixSumTestComputeShader;
120  auto context = GetContext();
121  auto host_buffer = CreateHostBufferFromContext(context);
122  ASSERT_TRUE(context);
123  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
124 
125  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
126  auto pipeline_desc =
127  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
128  ASSERT_TRUE(pipeline_desc.has_value());
129  auto compute_pipeline =
130  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
131  ASSERT_TRUE(compute_pipeline);
132 
133  auto cmd_buffer = context->CreateCommandBuffer();
134  auto pass = cmd_buffer->CreateComputePass();
135  ASSERT_TRUE(pass && pass->IsValid());
136 
137  static constexpr size_t kCount = 5;
138 
139  pass->SetPipeline(compute_pipeline);
140 
141  CS::InputData<kCount> input_data;
142  input_data.count = kCount;
143  for (size_t i = 0; i < kCount; i++) {
144  input_data.data[i] = 1 + i;
145  }
146 
147  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::OutputData<kCount>>(
148  context, "Output Buffer");
149 
150  CS::BindInputData(*pass, host_buffer->EmplaceStorageBuffer(input_data));
151  CS::BindOutputData(*pass, DeviceBuffer::AsBufferView(output_buffer));
152 
153  ASSERT_TRUE(pass->Compute(ISize(kCount, 1)).ok());
154  ASSERT_TRUE(pass->EncodeCommands());
155 
156  fml::AutoResetWaitableEvent latch;
157  ASSERT_TRUE(
158  context->GetCommandQueue()
159  ->Submit({cmd_buffer},
160  [&latch, output_buffer](CommandBuffer::Status status) {
161  EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
162 
163  auto view = DeviceBuffer::AsBufferView(output_buffer);
164  EXPECT_EQ(view.GetRange().length,
165  sizeof(CS::OutputData<kCount>));
166 
167  CS::OutputData<kCount>* output =
168  reinterpret_cast<CS::OutputData<kCount>*>(
169  output_buffer->OnGetContents());
170  EXPECT_TRUE(output);
171 
172  constexpr uint32_t expected[kCount] = {1, 3, 6, 10, 15};
173  for (size_t i = 0; i < kCount; i++) {
174  auto computed_sum = output->data[i];
175  EXPECT_EQ(computed_sum, expected[i]);
176  }
177  latch.Signal();
178  })
179  .ok());
180 
181  latch.Wait();
182 }
183 
184 TEST_P(ComputeTest, 1DThreadgroupSizingIsCorrect) {
185  using CS = ThreadgroupSizingTestComputeShader;
186  auto context = GetContext();
187  ASSERT_TRUE(context);
188  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
189 
190  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
191  auto pipeline_desc =
192  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
193  ASSERT_TRUE(pipeline_desc.has_value());
194  auto compute_pipeline =
195  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
196  ASSERT_TRUE(compute_pipeline);
197 
198  auto cmd_buffer = context->CreateCommandBuffer();
199  auto pass = cmd_buffer->CreateComputePass();
200  ASSERT_TRUE(pass && pass->IsValid());
201 
202  static constexpr size_t kCount = 2048;
203 
204  pass->SetPipeline(compute_pipeline);
205 
206  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::OutputData<kCount>>(
207  context, "Output Buffer");
208 
209  CS::BindOutputData(*pass, DeviceBuffer::AsBufferView(output_buffer));
210 
211  ASSERT_TRUE(pass->Compute(ISize(kCount, 1)).ok());
212  ASSERT_TRUE(pass->EncodeCommands());
213 
214  fml::AutoResetWaitableEvent latch;
215  ASSERT_TRUE(
216  context->GetCommandQueue()
217  ->Submit({cmd_buffer},
218  [&latch, output_buffer](CommandBuffer::Status status) {
219  EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
220 
221  auto view = DeviceBuffer::AsBufferView(output_buffer);
222  EXPECT_EQ(view.GetRange().length,
223  sizeof(CS::OutputData<kCount>));
224 
225  CS::OutputData<kCount>* output =
226  reinterpret_cast<CS::OutputData<kCount>*>(
227  output_buffer->OnGetContents());
228  EXPECT_TRUE(output);
229  EXPECT_EQ(output->data[kCount - 1], kCount - 1);
230  latch.Signal();
231  })
232  .ok());
233 
234  latch.Wait();
235 }
236 
237 TEST_P(ComputeTest, CanComputePrefixSumLargeInteractive) {
238  using CS = PrefixSumTestComputeShader;
239 
240  auto context = GetContext();
241  auto host_buffer = CreateHostBufferFromContext(context);
242 
243  ASSERT_TRUE(context);
244  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
245 
246  auto callback = [&](RenderPass& render_pass) -> bool {
247  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
248  auto pipeline_desc =
249  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
250  auto compute_pipeline =
251  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
252 
253  auto cmd_buffer = context->CreateCommandBuffer();
254  auto pass = cmd_buffer->CreateComputePass();
255 
256  static constexpr size_t kCount = 1023;
257 
258  pass->SetPipeline(compute_pipeline);
259 
260  CS::InputData<kCount> input_data;
261  input_data.count = kCount;
262  for (size_t i = 0; i < kCount; i++) {
263  input_data.data[i] = 1 + i;
264  }
265 
266  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::OutputData<kCount>>(
267  context, "Output Buffer");
268 
269  CS::BindInputData(*pass, host_buffer->EmplaceStorageBuffer(input_data));
270  CS::BindOutputData(*pass, DeviceBuffer::AsBufferView(output_buffer));
271 
272  pass->Compute(ISize(kCount, 1));
273  pass->EncodeCommands();
274  host_buffer->Reset();
275  return context->GetCommandQueue()->Submit({cmd_buffer}).ok();
276  };
277  ASSERT_TRUE(OpenPlaygroundHere(callback));
278 }
279 
280 TEST_P(ComputeTest, MultiStageInputAndOutput) {
281  using CS1 = Stage1ComputeShader;
282  using Stage1PipelineBuilder = ComputePipelineBuilder<CS1>;
283  using CS2 = Stage2ComputeShader;
284  using Stage2PipelineBuilder = ComputePipelineBuilder<CS2>;
285 
286  auto context = GetContext();
287  auto host_buffer = CreateHostBufferFromContext(context);
288  ASSERT_TRUE(context);
289  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
290 
291  auto pipeline_desc_1 =
292  Stage1PipelineBuilder::MakeDefaultPipelineDescriptor(*context);
293  ASSERT_TRUE(pipeline_desc_1.has_value());
294  auto compute_pipeline_1 =
295  context->GetPipelineLibrary()->GetPipeline(pipeline_desc_1).Get();
296  ASSERT_TRUE(compute_pipeline_1);
297 
298  auto pipeline_desc_2 =
299  Stage2PipelineBuilder::MakeDefaultPipelineDescriptor(*context);
300  ASSERT_TRUE(pipeline_desc_2.has_value());
301  auto compute_pipeline_2 =
302  context->GetPipelineLibrary()->GetPipeline(pipeline_desc_2).Get();
303  ASSERT_TRUE(compute_pipeline_2);
304 
305  auto cmd_buffer = context->CreateCommandBuffer();
306  auto pass = cmd_buffer->CreateComputePass();
307  ASSERT_TRUE(pass && pass->IsValid());
308 
309  static constexpr size_t kCount1 = 5;
310  static constexpr size_t kCount2 = kCount1 * 2;
311 
312  CS1::Input<kCount1> input_1;
313  input_1.count = kCount1;
314  for (size_t i = 0; i < kCount1; i++) {
315  input_1.elements[i] = i;
316  }
317 
318  CS2::Input<kCount2> input_2;
319  input_2.count = kCount2;
320  for (size_t i = 0; i < kCount2; i++) {
321  input_2.elements[i] = i;
322  }
323 
324  auto output_buffer_1 = CreateHostVisibleDeviceBuffer<CS1::Output<kCount2>>(
325  context, "Output Buffer Stage 1");
326  auto output_buffer_2 = CreateHostVisibleDeviceBuffer<CS2::Output<kCount2>>(
327  context, "Output Buffer Stage 2");
328 
329  {
330  pass->SetPipeline(compute_pipeline_1);
331 
332  CS1::BindInput(*pass, host_buffer->EmplaceStorageBuffer(input_1));
333  CS1::BindOutput(*pass, DeviceBuffer::AsBufferView(output_buffer_1));
334 
335  ASSERT_TRUE(pass->Compute(ISize(512, 1)).ok());
336  pass->AddBufferMemoryBarrier();
337  }
338 
339  {
340  pass->SetPipeline(compute_pipeline_2);
341 
342  CS1::BindInput(*pass, DeviceBuffer::AsBufferView(output_buffer_1));
343  CS2::BindOutput(*pass, DeviceBuffer::AsBufferView(output_buffer_2));
344  ASSERT_TRUE(pass->Compute(ISize(512, 1)).ok());
345  }
346 
347  ASSERT_TRUE(pass->EncodeCommands());
348 
349  fml::AutoResetWaitableEvent latch;
350  ASSERT_TRUE(
351  context->GetCommandQueue()
352  ->Submit({cmd_buffer},
353  [&latch, &output_buffer_1,
354  &output_buffer_2](CommandBuffer::Status status) {
355  EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
356 
357  CS1::Output<kCount2>* output_1 =
358  reinterpret_cast<CS1::Output<kCount2>*>(
359  output_buffer_1->OnGetContents());
360  EXPECT_TRUE(output_1);
361  EXPECT_EQ(output_1->count, 10u);
362  EXPECT_THAT(
363  output_1->elements,
364  ::testing::ElementsAre(0, 0, 2, 3, 4, 6, 6, 9, 8, 12));
365 
366  CS2::Output<kCount2>* output_2 =
367  reinterpret_cast<CS2::Output<kCount2>*>(
368  output_buffer_2->OnGetContents());
369  EXPECT_TRUE(output_2);
370  EXPECT_EQ(output_2->count, 10u);
371  EXPECT_THAT(output_2->elements,
372  ::testing::ElementsAre(0, 0, 4, 6, 8, 12, 12,
373  18, 16, 24));
374 
375  latch.Signal();
376  })
377  .ok());
378 
379  latch.Wait();
380 }
381 
382 TEST_P(ComputeTest, CanCompute1DimensionalData) {
383  using CS = SampleComputeShader;
384  auto context = GetContext();
385  auto host_buffer = CreateHostBufferFromContext(context);
386  ASSERT_TRUE(context);
387  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
388 
389  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
390  auto pipeline_desc =
391  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
392  ASSERT_TRUE(pipeline_desc.has_value());
393  auto compute_pipeline =
394  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
395  ASSERT_TRUE(compute_pipeline);
396 
397  auto cmd_buffer = context->CreateCommandBuffer();
398  auto pass = cmd_buffer->CreateComputePass();
399  ASSERT_TRUE(pass && pass->IsValid());
400 
401  static constexpr size_t kCount = 5;
402 
403  pass->SetPipeline(compute_pipeline);
404 
405  CS::Info info{.count = kCount};
406  CS::Input0<kCount> input_0;
407  CS::Input1<kCount> input_1;
408  for (size_t i = 0; i < kCount; i++) {
409  input_0.elements[i] = Vector4(2.0 + i, 3.0 + i, 4.0 + i, 5.0 * i);
410  input_1.elements[i] = Vector4(6.0, 7.0, 8.0, 9.0);
411  }
412 
413  input_0.fixed_array[1] = IPoint32(2, 2);
414  input_1.fixed_array[0] = UintPoint32(3, 3);
415  input_0.some_int = 5;
416  input_1.some_struct = CS::SomeStruct{.vf = Point(3, 4), .i = 42};
417 
418  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::Output<kCount>>(
419  context, "Output Buffer");
420 
421  CS::BindInfo(*pass, host_buffer->EmplaceUniform(info));
422  CS::BindInput0(*pass, host_buffer->EmplaceStorageBuffer(input_0));
423  CS::BindInput1(*pass, host_buffer->EmplaceStorageBuffer(input_1));
424  CS::BindOutput(*pass, DeviceBuffer::AsBufferView(output_buffer));
425 
426  ASSERT_TRUE(pass->Compute(ISize(kCount, 1)).ok());
427  ASSERT_TRUE(pass->EncodeCommands());
428 
429  fml::AutoResetWaitableEvent latch;
430  ASSERT_TRUE(
431  context->GetCommandQueue()
432  ->Submit(
433  {cmd_buffer},
434  [&latch, output_buffer, &input_0,
435  &input_1](CommandBuffer::Status status) {
436  EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
437 
438  auto view = DeviceBuffer::AsBufferView(output_buffer);
439  EXPECT_EQ(view.GetRange().length, sizeof(CS::Output<kCount>));
440 
441  CS::Output<kCount>* output =
442  reinterpret_cast<CS::Output<kCount>*>(
443  output_buffer->OnGetContents());
444  EXPECT_TRUE(output);
445  for (size_t i = 0; i < kCount; i++) {
446  Vector4 vector = output->elements[i];
447  Vector4 computed = input_0.elements[i] * input_1.elements[i];
448  EXPECT_EQ(vector,
449  Vector4(computed.x + 2 + input_1.some_struct.i,
450  computed.y + 3 + input_1.some_struct.vf.x,
451  computed.z + 5 + input_1.some_struct.vf.y,
452  computed.w));
453  }
454  latch.Signal();
455  })
456  .ok());
457 
458  latch.Wait();
459 }
460 
461 TEST_P(ComputeTest, ReturnsEarlyWhenAnyGridDimensionIsZero) {
462  using CS = SampleComputeShader;
463  auto context = GetContext();
464  auto host_buffer = CreateHostBufferFromContext(context);
465  ASSERT_TRUE(context);
466  ASSERT_TRUE(context->GetCapabilities()->SupportsCompute());
467 
468  using SamplePipelineBuilder = ComputePipelineBuilder<CS>;
469  auto pipeline_desc =
470  SamplePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
471  ASSERT_TRUE(pipeline_desc.has_value());
472  auto compute_pipeline =
473  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
474  ASSERT_TRUE(compute_pipeline);
475 
476  auto cmd_buffer = context->CreateCommandBuffer();
477  auto pass = cmd_buffer->CreateComputePass();
478  ASSERT_TRUE(pass && pass->IsValid());
479 
480  static constexpr size_t kCount = 5;
481 
482  pass->SetPipeline(compute_pipeline);
483 
484  CS::Info info{.count = kCount};
485  CS::Input0<kCount> input_0;
486  CS::Input1<kCount> input_1;
487  for (size_t i = 0; i < kCount; i++) {
488  input_0.elements[i] = Vector4(2.0 + i, 3.0 + i, 4.0 + i, 5.0 * i);
489  input_1.elements[i] = Vector4(6.0, 7.0, 8.0, 9.0);
490  }
491 
492  input_0.fixed_array[1] = IPoint32(2, 2);
493  input_1.fixed_array[0] = UintPoint32(3, 3);
494  input_0.some_int = 5;
495  input_1.some_struct = CS::SomeStruct{.vf = Point(3, 4), .i = 42};
496 
497  auto output_buffer = CreateHostVisibleDeviceBuffer<CS::Output<kCount>>(
498  context, "Output Buffer");
499 
500  CS::BindInfo(*pass, host_buffer->EmplaceUniform(info));
501  CS::BindInput0(*pass, host_buffer->EmplaceStorageBuffer(input_0));
502  CS::BindInput1(*pass, host_buffer->EmplaceStorageBuffer(input_1));
503  CS::BindOutput(*pass, DeviceBuffer::AsBufferView(output_buffer));
504 
505  // Intentionally making the grid size zero in one dimension. No GPU will
506  // tolerate this.
507  EXPECT_FALSE(pass->Compute(ISize(0, 1)).ok());
508  pass->EncodeCommands();
509 }
510 
511 } // namespace testing
512 } // namespace impeller
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter, size_t minimum_uniform_alignment)
Definition: host_buffer.cc:21
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
TEST_P(ComputeTest, ReturnsEarlyWhenAnyGridDimensionIsZero)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_COMPUTE_SUITE(ComputeTest)
TPoint< Scalar > Point
Definition: point.h:425
TPoint< int32_t > IPoint32
Definition: point.h:427
ISize64 ISize
Definition: size.h:162
TPoint< uint32_t > UintPoint32
Definition: point.h:428
An optional (but highly recommended) utility for creating pipelines from reflected shader information...