| 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"
8 #include "impeller/core/host_buffer.h"
9 #include "impeller/fixtures/sample.comp.h"
10 #include "impeller/fixtures/stage1.comp.h"
11 #include "impeller/fixtures/stage2.comp.h"
12 #include "impeller/playground/compute_playground_test.h"
13 #include "impeller/renderer/command_buffer.h"
14 #include "impeller/renderer/compute_pipeline_builder.h"
15 #include "impeller/renderer/pipeline_library.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) {
22 return impeller::HostBuffer::Create(
23 context->GetResourceAllocator(), context->GetIdleWaiter(),
24 context->GetCapabilities()->GetMinimumUniformAlignment());
25 }
26 } // namespace
27
28 namespace impeller {
29 namespace testing {
30 using ComputeTest = ComputePlaygroundTest;
31 INSTANTIATE_COMPUTE_SUITE(ComputeTest);
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
impeller::CommandBuffer::Status
Status
Definition: command_buffer.h:49
impeller::ComputePlaygroundTest
Definition: compute_playground_test.h:19
impeller::DeviceBuffer::AsBufferView
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
Definition: device_buffer.cc:18
impeller::HostBuffer::Create
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
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
command_buffer.h
compute_pipeline_builder.h
compute_playground_test.h
host_buffer.h
impeller::testing::TEST_P
TEST_P(ComputeTest, ReturnsEarlyWhenAnyGridDimensionIsZero)
Definition: compute_unittests.cc:461
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
impeller::testing::INSTANTIATE_COMPUTE_SUITE
INSTANTIATE_COMPUTE_SUITE(ComputeTest)
impeller
Definition: allocation.cc:12
impeller::Point
TPoint< Scalar > Point
Definition: point.h:425
impeller::IPoint32
TPoint< int32_t > IPoint32
Definition: point.h:427
impeller::ISize
ISize64 ISize
Definition: size.h:162
impeller::UintPoint32
TPoint< uint32_t > UintPoint32
Definition: point.h:428
impeller::SampleCount::kCount1
@ kCount1
pipeline_library.h
impeller::ComputePipelineBuilder
An optional (but highly recommended) utility for creating pipelines from reflected shader information...
Definition: compute_pipeline_builder.h:26
impeller::TSize
Definition: size.h:25
impeller::Vector4
Definition: vector.h:232