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