| 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"
9 #include "impeller/base/strings.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"
15 #include "impeller/geometry/path_component.h"
16 #include "impeller/playground/compute_playground_test.h"
17 #include "impeller/renderer/command_buffer.h"
18 #include "impeller/renderer/compute_command.h"
19 #include "impeller/renderer/compute_pipeline_builder.h"
20 #include "impeller/renderer/pipeline_library.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 {
26 using ComputeTest = ComputePlaygroundTest;
27 INSTANTIATE_COMPUTE_SUITE(ComputeTest);
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