1 #include <gtest/gtest.h>
2 
3 #include "CompositorVk.h"
4 
5 #include <algorithm>
6 #include <array>
7 #include <filesystem>
8 #include <glm/gtx/matrix_transform_2d.hpp>
9 #include <memory>
10 #include <optional>
11 
12 #include "BorrowedImageVk.h"
13 #include "aemu/base/synchronization/Lock.h"
14 #include "gfxstream/ImageUtils.h"
15 #include "tests/VkTestUtils.h"
16 #include "vulkan/VulkanDispatch.h"
17 #include "vulkan/vk_util.h"
18 
19 namespace gfxstream {
20 namespace vk {
21 namespace {
22 
23 static constexpr const bool kDefaultSaveImageIfComparisonFailed = false;
24 
GetTestDataPath(const std::string & basename)25 std::string GetTestDataPath(const std::string& basename) {
26     const std::filesystem::path currentPath = android::base::getProgramDirectory();
27     return (currentPath / "tests" / "testdata" / basename).string();
28 }
29 
30 static constexpr const uint32_t kColorBlack = 0xFF000000;
31 static constexpr const uint32_t kColorRed = 0xFF0000FF;
32 static constexpr const uint32_t kColorGreen = 0xFF00FF00;
33 static constexpr const uint32_t kDefaultImageWidth = 256;
34 static constexpr const uint32_t kDefaultImageHeight = 256;
35 
36 class CompositorVkTest : public ::testing::Test {
37    protected:
38     using TargetImage = RenderResourceVk<VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
39                                          VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT>;
40     using SourceImage = RenderTextureVk;
41 
SetUpTestCase()42     static void SetUpTestCase() { k_vk = vkDispatch(false); }
43 
SetUp()44     void SetUp() override {
45 #if defined(__APPLE__) && defined(__arm64__)
46         GTEST_SKIP() << "Skipping all test on Apple M2, as they are failing, see b/263494782";
47 #endif
48         ASSERT_NE(k_vk, nullptr);
49         createInstance();
50         pickPhysicalDevice();
51         createLogicalDevice();
52 
53         if (!supportsFeatures(TargetImage::k_vkFormat, VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) {
54             GTEST_SKIP() << "Skipping test as format " << TargetImage::k_vkFormat
55                          << " does not support VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT";
56         }
57         if (!supportsFeatures(SourceImage::k_vkFormat, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
58             GTEST_SKIP() << "Skipping test as format " << SourceImage::k_vkFormat
59                          << " does not support VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT";
60         }
61 
62         const VkCommandPoolCreateInfo commandPoolCi = {
63             .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
64             .queueFamilyIndex = m_compositorQueueFamilyIndex,
65         };
66         ASSERT_EQ(k_vk->vkCreateCommandPool(m_vkDevice, &commandPoolCi, nullptr, &m_vkCommandPool),
67                   VK_SUCCESS);
68 
69         k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0, &m_compositorVkQueue);
70         ASSERT_NE(m_compositorVkQueue, VK_NULL_HANDLE);
71 
72         m_compositorVkQueueLock = std::make_shared<android::base::Lock>();
73     }
74 
TearDown()75     void TearDown() override {
76 #if defined(__APPLE__) && defined(__arm64__)
77         return;
78 #endif
79 
80         k_vk->vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr);
81         k_vk->vkDestroyDevice(m_vkDevice, nullptr);
82         m_vkDevice = VK_NULL_HANDLE;
83         k_vk->vkDestroyInstance(m_vkInstance, nullptr);
84         m_vkInstance = VK_NULL_HANDLE;
85     }
86 
createCompositor()87     std::unique_ptr<CompositorVk> createCompositor() {
88         return CompositorVk::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
89                                     m_compositorVkQueueLock, m_compositorQueueFamilyIndex,
90                                     /*maxFramesInFlight=*/3);
91     }
92 
93     template <typename SourceOrTargetImage>
createImageWithColor(uint32_t sourceWidth,uint32_t sourceHeight,uint32_t sourceColor)94     std::unique_ptr<const SourceOrTargetImage> createImageWithColor(uint32_t sourceWidth,
95                                                                     uint32_t sourceHeight,
96                                                                     uint32_t sourceColor) {
97         auto source =
98             SourceOrTargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
99                                         m_vkCommandPool, sourceWidth, sourceHeight);
100         if (source == nullptr) {
101             return nullptr;
102         }
103 
104         std::vector<uint32_t> sourcePixels(sourceWidth * sourceHeight, sourceColor);
105         if (!source->write(sourcePixels)) {
106             return nullptr;
107         }
108 
109         return source;
110     }
111 
createSourceImageFromPng(const std::string & filename)112     std::unique_ptr<const SourceImage> createSourceImageFromPng(const std::string& filename) {
113         uint32_t sourceWidth;
114         uint32_t sourceHeight;
115         std::vector<uint32_t> sourcePixels;
116         if (!LoadRGBAFromPng(filename, &sourceWidth, &sourceHeight, &sourcePixels)) {
117             return nullptr;
118         }
119 
120         auto source =
121             SourceImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
122                                 m_vkCommandPool, sourceWidth, sourceHeight);
123         if (source == nullptr) {
124             return nullptr;
125         }
126 
127         if (!source->write(sourcePixels)) {
128             return nullptr;
129         }
130 
131         return source;
132     }
133 
isRGBAPixelNear(uint32_t actualPixel,uint32_t expectedPixel)134     bool isRGBAPixelNear(uint32_t actualPixel, uint32_t expectedPixel) {
135         const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel);
136         const uint8_t* expectedRGBA = reinterpret_cast<const uint8_t*>(&expectedPixel);
137 
138         constexpr const uint32_t kRGBA8888Tolerance = 2;
139         for (uint32_t channel = 0; channel < 4; channel++) {
140             const uint8_t actualChannel = actualRGBA[channel];
141             const uint8_t expectedChannel = expectedRGBA[channel];
142 
143             if ((std::max(actualChannel, expectedChannel) -
144                  std::min(actualChannel, expectedChannel)) > kRGBA8888Tolerance) {
145                 return false;
146             }
147         }
148         return true;
149     }
150 
compareRGBAPixels(const uint32_t * actualPixels,const uint32_t * expectedPixels,const uint32_t width,const uint32_t height)151     bool compareRGBAPixels(const uint32_t* actualPixels, const uint32_t* expectedPixels,
152                            const uint32_t width, const uint32_t height) {
153         bool comparisonFailed = false;
154 
155         uint32_t reportedIncorrectPixels = 0;
156         constexpr const uint32_t kMaxReportedIncorrectPixels = 10;
157 
158         for (uint32_t y = 0; y < width; y++) {
159             for (uint32_t x = 0; x < height; x++) {
160                 const uint32_t actualPixel = actualPixels[y * height + x];
161                 const uint32_t expectedPixel = expectedPixels[y * width + x];
162                 if (!isRGBAPixelNear(actualPixel, expectedPixel)) {
163                     comparisonFailed = true;
164                     if (reportedIncorrectPixels < kMaxReportedIncorrectPixels) {
165                         reportedIncorrectPixels++;
166                         const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel);
167                         const uint8_t* expectedRGBA =
168                             reinterpret_cast<const uint8_t*>(&expectedPixel);
169                         ADD_FAILURE()
170                             << "Pixel comparison failed at (" << x << ", " << y << ") "
171                             << " with actual "
172                             << " r:" << static_cast<int>(actualRGBA[0])
173                             << " g:" << static_cast<int>(actualRGBA[1])
174                             << " b:" << static_cast<int>(actualRGBA[2])
175                             << " a:" << static_cast<int>(actualRGBA[3]) << " but expected "
176                             << " r:" << static_cast<int>(expectedRGBA[0])
177                             << " g:" << static_cast<int>(expectedRGBA[1])
178                             << " b:" << static_cast<int>(expectedRGBA[2])
179                             << " a:" << static_cast<int>(expectedRGBA[3]);
180                     }
181                 }
182             }
183         }
184         return comparisonFailed;
185     }
186 
compareImageWithGoldenPng(const TargetImage * target,const std::string & filename,const bool saveImageIfComparisonFailed)187     void compareImageWithGoldenPng(const TargetImage* target, const std::string& filename,
188                                    const bool saveImageIfComparisonFailed) {
189         const uint32_t targetWidth = target->m_width;
190         const uint32_t targetHeight = target->m_height;
191         const auto targetPixelsOpt = target->read();
192         ASSERT_TRUE(targetPixelsOpt.has_value());
193         const auto& targetPixels = *targetPixelsOpt;
194 
195         uint32_t goldenWidth;
196         uint32_t goldenHeight;
197         std::vector<uint32_t> goldenPixels;
198         const bool loadedGolden =
199             LoadRGBAFromPng(filename, &goldenWidth, &goldenHeight, &goldenPixels);
200         EXPECT_TRUE(loadedGolden) << "Failed to load golden image from " << filename;
201 
202         bool comparisonFailed = !loadedGolden;
203 
204         if (loadedGolden) {
205             EXPECT_EQ(target->m_width, goldenWidth)
206                 << "Invalid width comparison with golden image from " << filename;
207             EXPECT_EQ(target->m_height, goldenHeight)
208                 << "Invalid height comparison with golden image from " << filename;
209             if (targetWidth != goldenWidth || targetHeight != goldenHeight) {
210                 comparisonFailed = true;
211             }
212             if (!comparisonFailed) {
213                 comparisonFailed = compareRGBAPixels(targetPixels.data(), goldenPixels.data(),
214                                                      goldenWidth, goldenHeight);
215             }
216         }
217 
218         if (saveImageIfComparisonFailed && comparisonFailed) {
219             const std::string output = (std::filesystem::temp_directory_path() /
220                                         std::filesystem::path(filename).filename())
221                                            .string();
222             SaveRGBAToPng(targetWidth, targetHeight, targetPixels.data(), output);
223             ADD_FAILURE() << "Saved composition result to " << output;
224         }
225     }
226 
227     template <typename SourceOrTargetImage>
createBorrowedImageInfo(const SourceOrTargetImage * image)228     std::unique_ptr<BorrowedImageInfoVk> createBorrowedImageInfo(const SourceOrTargetImage* image) {
229         static int sImageId = 0;
230 
231         auto ret = std::make_unique<BorrowedImageInfoVk>();
232         ret->id = sImageId++;
233         ret->width = image->m_width;
234         ret->height = image->m_height;
235         ret->image = image->m_vkImage;
236         ret->imageCreateInfo = image->m_vkImageCreateInfo;
237         ret->imageView = image->m_vkImageView;
238         ret->preBorrowLayout = SourceOrTargetImage::k_vkImageLayout;
239         ret->preBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex;
240         ret->postBorrowLayout = SourceOrTargetImage::k_vkImageLayout;
241         ret->postBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex;
242         return ret;
243     }
244 
checkImageFilledWith(const TargetImage * image,uint32_t expectedColor)245     void checkImageFilledWith(const TargetImage* image, uint32_t expectedColor) {
246         auto actualPixelsOpt = image->read();
247         ASSERT_TRUE(actualPixelsOpt.has_value());
248         auto& actualPixels = *actualPixelsOpt;
249 
250         const std::vector<uint32_t> expectedPixels(image->numOfPixels(), expectedColor);
251         compareRGBAPixels(actualPixels.data(), expectedPixels.data(), image->m_width,
252                           image->m_height);
253     }
254 
fillImageWith(const TargetImage * image,uint32_t color)255     void fillImageWith(const TargetImage* image, uint32_t color) {
256         const std::vector<uint32_t> pixels(image->numOfPixels(), color);
257         ASSERT_TRUE(image->write(pixels)) << "Failed to fill image with color:" << color;
258         checkImageFilledWith(image, color);
259     }
260 
261     static const VulkanDispatch* k_vk;
262     VkInstance m_vkInstance = VK_NULL_HANDLE;
263     VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE;
264     uint32_t m_compositorQueueFamilyIndex = 0;
265     VkDevice m_vkDevice = VK_NULL_HANDLE;
266     VkCommandPool m_vkCommandPool = VK_NULL_HANDLE;
267     VkQueue m_compositorVkQueue = VK_NULL_HANDLE;
268     std::shared_ptr<android::base::Lock> m_compositorVkQueueLock;
269 
270    private:
createInstance()271     void createInstance() {
272         const VkApplicationInfo appInfo = {
273             .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
274             .pNext = nullptr,
275             .pApplicationName = "emulator CompositorVk unittest",
276             .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
277             .pEngineName = "No Engine",
278             .engineVersion = VK_MAKE_VERSION(1, 0, 0),
279             .apiVersion = VK_API_VERSION_1_1,
280         };
281         const VkInstanceCreateInfo instanceCi = {
282             .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
283             .pApplicationInfo = &appInfo,
284             .enabledExtensionCount = 0,
285             .ppEnabledExtensionNames = nullptr,
286         };
287         ASSERT_EQ(k_vk->vkCreateInstance(&instanceCi, nullptr, &m_vkInstance), VK_SUCCESS);
288         ASSERT_NE(m_vkInstance, VK_NULL_HANDLE);
289     }
290 
pickPhysicalDevice()291     void pickPhysicalDevice() {
292         uint32_t physicalDeviceCount = 0;
293         ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, nullptr),
294                   VK_SUCCESS);
295         ASSERT_GT(physicalDeviceCount, 0);
296         std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
297         ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount,
298                                                    physicalDevices.data()),
299                   VK_SUCCESS);
300         for (const auto &device : physicalDevices) {
301             uint32_t queueFamilyCount = 0;
302             k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
303             ASSERT_GT(queueFamilyCount, 0);
304             std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
305             k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount,
306                                                            queueFamilyProperties.data());
307             uint32_t queueFamilyIndex = 0;
308             for (; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) {
309                 if (CompositorVk::queueSupportsComposition(
310                         queueFamilyProperties[queueFamilyIndex])) {
311                     break;
312                 }
313             }
314             if (queueFamilyIndex == queueFamilyCount) {
315                 continue;
316             }
317 
318             m_compositorQueueFamilyIndex = queueFamilyIndex;
319             m_vkPhysicalDevice = device;
320             return;
321         }
322         FAIL() << "Can't find a suitable VkPhysicalDevice.";
323     }
324 
createLogicalDevice()325     void createLogicalDevice() {
326         const float queuePriority = 1.0f;
327         const VkDeviceQueueCreateInfo queueCi = {
328             .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
329             .queueFamilyIndex = m_compositorQueueFamilyIndex,
330             .queueCount = 1,
331             .pQueuePriorities = &queuePriority,
332         };
333         const VkPhysicalDeviceFeatures2 features = {
334             .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
335             .pNext = nullptr,
336         };
337         const VkDeviceCreateInfo deviceCi = {
338             .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
339             .pNext = &features,
340             .queueCreateInfoCount = 1,
341             .pQueueCreateInfos = &queueCi,
342             .enabledLayerCount = 0,
343             .enabledExtensionCount = 0,
344             .ppEnabledExtensionNames = nullptr,
345         };
346         ASSERT_EQ(k_vk->vkCreateDevice(m_vkPhysicalDevice, &deviceCi, nullptr, &m_vkDevice),
347                   VK_SUCCESS);
348         ASSERT_NE(m_vkDevice, VK_NULL_HANDLE);
349     }
350 
supportsFeatures(VkFormat format,VkFormatFeatureFlags features)351     bool supportsFeatures(VkFormat format, VkFormatFeatureFlags features) {
352         VkFormatProperties formatProperties = {};
353         k_vk->vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice, format, &formatProperties);
354         return (formatProperties.optimalTilingFeatures & features) == features;
355     }
356 };
357 
358 const VulkanDispatch* CompositorVkTest::k_vk = nullptr;
359 
TEST_F(CompositorVkTest,QueueSupportsComposition)360 TEST_F(CompositorVkTest, QueueSupportsComposition) {
361     VkQueueFamilyProperties properties = {};
362 
363     properties.queueFlags &= ~VK_QUEUE_GRAPHICS_BIT;
364     ASSERT_FALSE(CompositorVk::queueSupportsComposition(properties));
365 
366     properties.queueFlags |= VK_QUEUE_GRAPHICS_BIT;
367     ASSERT_TRUE(CompositorVk::queueSupportsComposition(properties));
368 }
369 
TEST_F(CompositorVkTest,Init)370 TEST_F(CompositorVkTest, Init) { ASSERT_NE(createCompositor(), nullptr); }
371 
TEST_F(CompositorVkTest,EmptyCompositionShouldDrawABlackFrame)372 TEST_F(CompositorVkTest, EmptyCompositionShouldDrawABlackFrame) {
373     auto compositor = createCompositor();
374     ASSERT_NE(compositor, nullptr);
375 
376     std::vector<std::unique_ptr<const TargetImage>> targets;
377     constexpr const uint32_t kNumImages = 10;
378     for (uint32_t i = 0; i < kNumImages; i++) {
379         auto target =
380             TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
381                                 m_vkCommandPool, kDefaultImageWidth, kDefaultImageHeight);
382         ASSERT_NE(target, nullptr);
383         fillImageWith(target.get(), kColorRed);
384         targets.emplace_back(std::move(target));
385     }
386 
387     for (uint32_t i = 0; i < kNumImages; i++) {
388         const Compositor::CompositionRequest compositionRequest = {
389             .target = createBorrowedImageInfo(targets[i].get()),
390             .layers = {},  // Note: this is empty!
391         };
392 
393         auto compositionCompleteWaitable = compositor->compose(compositionRequest);
394         compositionCompleteWaitable.wait();
395     }
396 
397     for (const auto& target : targets) {
398         checkImageFilledWith(target.get(), kColorBlack);
399     }
400 }
401 
TEST_F(CompositorVkTest,SimpleComposition)402 TEST_F(CompositorVkTest, SimpleComposition) {
403     auto compositor = createCompositor();
404     ASSERT_NE(compositor, nullptr);
405 
406     auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png"));
407     ASSERT_NE(source, nullptr);
408 
409     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
410                                       m_vkCommandPool, source->m_width, source->m_height);
411     ASSERT_NE(target, nullptr);
412     fillImageWith(target.get(), kColorBlack);
413 
414     Compositor::CompositionRequest compositionRequest = {
415         .target = createBorrowedImageInfo(target.get()),
416     };
417     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
418         .source = createBorrowedImageInfo(source.get()),
419         .props =
420             {
421                 .composeMode = HWC2_COMPOSITION_DEVICE,
422                 .displayFrame =
423                     {
424                         .left = 64,
425                         .top = 32,
426                         .right = 128,
427                         .bottom = 160,
428                     },
429                 .crop =
430                     {
431                         .left = 0,
432                         .top = 0,
433                         .right = static_cast<float>(source->m_width),
434                         .bottom = static_cast<float>(source->m_height),
435                     },
436                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
437                 .alpha = 1.0,
438                 .color =
439                     {
440                         .r = 0,
441                         .g = 0,
442                         .b = 0,
443                         .a = 0,
444                     },
445                 .transform = HWC_TRANSFORM_NONE,
446             },
447     });
448 
449     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
450     compositionCompleteWaitable.wait();
451 
452     compareImageWithGoldenPng(target.get(),
453                               GetTestDataPath("256x256_golden_simple_composition.png"),
454                               kDefaultSaveImageIfComparisonFailed);
455 }
456 
TEST_F(CompositorVkTest,BlendPremultiplied)457 TEST_F(CompositorVkTest, BlendPremultiplied) {
458     auto compositor = createCompositor();
459     ASSERT_NE(compositor, nullptr);
460 
461     auto source =
462         createSourceImageFromPng(GetTestDataPath("256x256_android_with_transparency.png"));
463     ASSERT_NE(source, nullptr);
464 
465     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
466                                       m_vkCommandPool, source->m_width, source->m_height);
467     ASSERT_NE(target, nullptr);
468     fillImageWith(target.get(), kColorBlack);
469 
470     Compositor::CompositionRequest compositionRequest = {
471         .target = createBorrowedImageInfo(target.get()),
472     };
473     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
474         .source = createBorrowedImageInfo(source.get()),
475         .props =
476             {
477                 .composeMode = HWC2_COMPOSITION_DEVICE,
478                 .displayFrame =
479                     {
480                         .left = 0,
481                         .top = 0,
482                         .right = static_cast<int>(target->m_width),
483                         .bottom = static_cast<int>(target->m_height),
484                     },
485                 .crop =
486                     {
487                         .left = 0,
488                         .top = 0,
489                         .right = static_cast<float>(source->m_width),
490                         .bottom = static_cast<float>(source->m_height),
491                     },
492                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
493                 .alpha = 1.0,
494                 .color =
495                     {
496                         .r = 0,
497                         .g = 0,
498                         .b = 0,
499                         .a = 0,
500                     },
501                 .transform = HWC_TRANSFORM_NONE,
502             },
503     });
504 
505     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
506     compositionCompleteWaitable.wait();
507 
508     compareImageWithGoldenPng(target.get(),
509                               GetTestDataPath("256x256_golden_blend_premultiplied.png"),
510                               kDefaultSaveImageIfComparisonFailed);
511 }
512 
TEST_F(CompositorVkTest,Crop)513 TEST_F(CompositorVkTest, Crop) {
514     auto compositor = createCompositor();
515     ASSERT_NE(compositor, nullptr);
516 
517     auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png"));
518     ASSERT_NE(source, nullptr);
519 
520     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
521                                       m_vkCommandPool, source->m_width, source->m_height);
522     ASSERT_NE(target, nullptr);
523     fillImageWith(target.get(), kColorBlack);
524 
525     Compositor::CompositionRequest compositionRequest = {
526         .target = createBorrowedImageInfo(target.get()),
527     };
528     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
529         .source = createBorrowedImageInfo(source.get()),
530         .props =
531             {
532                 .composeMode = HWC2_COMPOSITION_DEVICE,
533                 .displayFrame =
534                     {
535                         .left = 0,
536                         .top = 0,
537                         .right = static_cast<int>(target->m_width),
538                         .bottom = static_cast<int>(target->m_height),
539                     },
540                 .crop =
541                     {
542                         .left = 0,
543                         .top = 0,
544                         .right = static_cast<float>(source->m_width) / 2.0f,
545                         .bottom = static_cast<float>(source->m_height) / 2.0f,
546                     },
547                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
548                 .alpha = 1.0,
549                 .color =
550                     {
551                         .r = 0,
552                         .g = 0,
553                         .b = 0,
554                         .a = 0,
555                     },
556                 .transform = HWC_TRANSFORM_NONE,
557             },
558     });
559 
560     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
561     compositionCompleteWaitable.wait();
562 
563     compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_crop.png"),
564                               kDefaultSaveImageIfComparisonFailed);
565 }
566 
TEST_F(CompositorVkTest,SolidColor)567 TEST_F(CompositorVkTest, SolidColor) {
568     auto compositor = createCompositor();
569     ASSERT_NE(compositor, nullptr);
570 
571     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
572                                       m_vkCommandPool, 256, 256);
573     ASSERT_NE(target, nullptr);
574     fillImageWith(target.get(), kColorBlack);
575 
576     Compositor::CompositionRequest compositionRequest = {
577         .target = createBorrowedImageInfo(target.get()),
578     };
579     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
580         .source = nullptr,
581         .props =
582             {
583                 .composeMode = HWC2_COMPOSITION_SOLID_COLOR,
584                 .displayFrame =
585                     {
586                         .left = 0,
587                         .top = 0,
588                         .right = static_cast<int>(target->m_width),
589                         .bottom = static_cast<int>(target->m_height),
590                     },
591                 .alpha = 0.75f,
592                 .color =
593                     {
594                         .r = 255,
595                         .g = 255,
596                         .b = 0,
597                         .a = 255,
598                     },
599             },
600     });
601 
602     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
603     compositionCompleteWaitable.wait();
604 
605     compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_solid_color.png"),
606                               kDefaultSaveImageIfComparisonFailed);
607 }
608 
TEST_F(CompositorVkTest,SolidColorBelow)609 TEST_F(CompositorVkTest, SolidColorBelow) {
610     auto compositor = createCompositor();
611     ASSERT_NE(compositor, nullptr);
612 
613     auto source =
614         createSourceImageFromPng(GetTestDataPath("256x256_android_with_transparency.png"));
615     ASSERT_NE(source, nullptr);
616 
617     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
618                                       m_vkCommandPool, source->m_width, source->m_height);
619     ASSERT_NE(target, nullptr);
620     fillImageWith(target.get(), kColorBlack);
621 
622     Compositor::CompositionRequest compositionRequest = {
623         .target = createBorrowedImageInfo(target.get()),
624     };
625     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
626         .source = nullptr,
627         .props =
628             {
629                 .composeMode = HWC2_COMPOSITION_SOLID_COLOR,
630                 .displayFrame =
631                     {
632                         .left = 0,
633                         .top = 0,
634                         .right = static_cast<int>(target->m_width),
635                         .bottom = static_cast<int>(target->m_height),
636                     },
637                 .alpha = 1.0f,
638                 .color =
639                     {
640                         .r = 0,
641                         .g = 0,
642                         .b = 255,
643                         .a = 255,
644                     },
645             },
646     });
647     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
648         .source = createBorrowedImageInfo(source.get()),
649         .props =
650             {
651                 .composeMode = HWC2_COMPOSITION_DEVICE,
652                 .displayFrame =
653                     {
654                         .left = 0,
655                         .top = 0,
656                         .right = static_cast<int>(target->m_width),
657                         .bottom = static_cast<int>(target->m_height),
658                     },
659                 .crop =
660                     {
661                         .left = 0,
662                         .top = 0,
663                         .right = static_cast<float>(source->m_width),
664                         .bottom = static_cast<float>(source->m_height),
665                     },
666                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
667                 .alpha = 1.0,
668                 .color =
669                     {
670                         .r = 0,
671                         .g = 0,
672                         .b = 0,
673                         .a = 0,
674                     },
675                 .transform = HWC_TRANSFORM_NONE,
676             },
677     });
678 
679     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
680     compositionCompleteWaitable.wait();
681 
682     compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_solid_color_below.png"),
683                               kDefaultSaveImageIfComparisonFailed);
684 }
685 
TEST_F(CompositorVkTest,SolidColorAbove)686 TEST_F(CompositorVkTest, SolidColorAbove) {
687     auto compositor = createCompositor();
688     ASSERT_NE(compositor, nullptr);
689 
690     auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png"));
691     ASSERT_NE(source, nullptr);
692 
693     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
694                                       m_vkCommandPool, source->m_width, source->m_height);
695     ASSERT_NE(target, nullptr);
696     fillImageWith(target.get(), kColorBlack);
697 
698     Compositor::CompositionRequest compositionRequest = {
699         .target = createBorrowedImageInfo(target.get()),
700     };
701     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
702         .source = createBorrowedImageInfo(source.get()),
703         .props =
704             {
705                 .composeMode = HWC2_COMPOSITION_DEVICE,
706                 .displayFrame =
707                     {
708                         .left = 0,
709                         .top = 0,
710                         .right = static_cast<int>(target->m_width),
711                         .bottom = static_cast<int>(target->m_height),
712                     },
713                 .crop =
714                     {
715                         .left = 0,
716                         .top = 0,
717                         .right = static_cast<float>(source->m_width),
718                         .bottom = static_cast<float>(source->m_height),
719                     },
720                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
721                 .alpha = 1.0,
722                 .color =
723                     {
724                         .r = 0,
725                         .g = 0,
726                         .b = 0,
727                         .a = 0,
728                     },
729                 .transform = HWC_TRANSFORM_NONE,
730             },
731     });
732     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
733         .source = nullptr,
734         .props =
735             {
736                 .composeMode = HWC2_COMPOSITION_SOLID_COLOR,
737                 .displayFrame =
738                     {
739                         .left = 0,
740                         .top = 0,
741                         .right = static_cast<int>(target->m_width),
742                         .bottom = static_cast<int>(target->m_height),
743                     },
744                 .alpha = 1.0f,
745                 .color =
746                     {
747                         .r = 0,
748                         .g = 255,
749                         .b = 0,
750                         .a = 127,
751                     },
752             },
753     });
754 
755     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
756     compositionCompleteWaitable.wait();
757 
758     compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_solid_color_above.png"),
759                               kDefaultSaveImageIfComparisonFailed);
760 }
761 
TEST_F(CompositorVkTest,Transformations)762 TEST_F(CompositorVkTest, Transformations) {
763     auto compositor = createCompositor();
764     ASSERT_NE(compositor, nullptr);
765 
766     auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png"));
767     ASSERT_NE(source, nullptr);
768 
769     Compositor::CompositionRequest compositionRequest;
770     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
771         .props =
772             {
773                 .composeMode = HWC2_COMPOSITION_DEVICE,
774                 .displayFrame =
775                     {
776                         .left = 32,
777                         .top = 32,
778                         .right = 224,
779                         .bottom = 224,
780                     },
781                 .crop =
782                     {
783                         .left = 0,
784                         .top = 0,
785                         .right = static_cast<float>(source->m_width),
786                         .bottom = static_cast<float>(source->m_height),
787                     },
788                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
789                 .alpha = 1.0,
790                 .color =
791                     {
792                         .r = 0,
793                         .g = 0,
794                         .b = 0,
795                         .a = 0,
796                     },
797                 .transform = HWC_TRANSFORM_NONE,
798             },
799     });
800 
801     const std::unordered_map<hwc_transform_t, std::string> transformToGolden = {
802         {HWC_TRANSFORM_NONE, "256x256_golden_transform_none.png"},
803         {HWC_TRANSFORM_FLIP_H, "256x256_golden_transform_fliph.png"},
804         {HWC_TRANSFORM_FLIP_V, "256x256_golden_transform_flipv.png"},
805         {HWC_TRANSFORM_ROT_90, "256x256_golden_transform_rot90.png"},
806         {HWC_TRANSFORM_ROT_180, "256x256_golden_transform_rot180.png"},
807         {HWC_TRANSFORM_ROT_270, "256x256_golden_transform_rot270.png"},
808         {HWC_TRANSFORM_FLIP_H_ROT_90, "256x256_golden_transform_fliphrot90.png"},
809         {HWC_TRANSFORM_FLIP_V_ROT_90, "256x256_golden_transform_flipvrot90.png"},
810     };
811 
812     for (const auto [transform, golden] : transformToGolden) {
813         auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice,
814                                           m_compositorVkQueue, m_vkCommandPool, 256, 256);
815         ASSERT_NE(target, nullptr);
816         fillImageWith(target.get(), kColorBlack);
817 
818         compositionRequest.target = createBorrowedImageInfo(target.get());
819         compositionRequest.layers[0].props.transform = transform;
820         compositionRequest.layers[0].source = createBorrowedImageInfo(source.get());
821 
822         auto compositionCompleteWaitable = compositor->compose(compositionRequest);
823         compositionCompleteWaitable.wait();
824 
825         compareImageWithGoldenPng(target.get(), GetTestDataPath(golden),
826                                   kDefaultSaveImageIfComparisonFailed);
827     }
828 }
829 
TEST_F(CompositorVkTest,MultipleTargetsComposition)830 TEST_F(CompositorVkTest, MultipleTargetsComposition) {
831     auto compositor = createCompositor();
832     ASSERT_NE(compositor, nullptr);
833 
834     constexpr const uint32_t kNumCompostions = 10;
835 
836     auto source = createImageWithColor<SourceImage>(256, 256, kColorGreen);
837     ASSERT_NE(source, nullptr);
838 
839     std::vector<std::unique_ptr<const TargetImage>> targets;
840     for (uint32_t i = 0; i < kNumCompostions; i++) {
841         auto target = createImageWithColor<TargetImage>(256, 256, kColorBlack);
842         ASSERT_NE(target, nullptr);
843         targets.emplace_back(std::move(target));
844     }
845 
846     Compositor::CompositionRequest compositionRequest = {};
847     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
848         .props =
849             {
850                 .composeMode = HWC2_COMPOSITION_DEVICE,
851                 .displayFrame =
852                     {
853                         .left = 0,
854                         .top = 0,
855                         .right = 0,
856                         .bottom = static_cast<int>(source->m_height),
857                     },
858                 .crop =
859                     {
860                         .left = 0,
861                         .top = 0,
862                         .right = static_cast<float>(source->m_width),
863                         .bottom = static_cast<float>(source->m_height),
864                     },
865                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
866                 .alpha = 1.0,
867                 .color =
868                     {
869                         .r = 0,
870                         .g = 0,
871                         .b = 0,
872                         .a = 0,
873                     },
874                 .transform = HWC_TRANSFORM_NONE,
875             },
876     });
877 
878     const uint32_t displayFrameWidth = 256 / kNumCompostions;
879     for (uint32_t i = 0; i < kNumCompostions; i++) {
880         const auto& target = targets[i];
881 
882         compositionRequest.target = createBorrowedImageInfo(target.get());
883         compositionRequest.layers[0].source = createBorrowedImageInfo(source.get()),
884         compositionRequest.layers[0].props.displayFrame.left = (i + 0) * displayFrameWidth;
885         compositionRequest.layers[0].props.displayFrame.right = (i + 1) * displayFrameWidth;
886 
887         auto compositionCompleteWaitable = compositor->compose(compositionRequest);
888         compositionCompleteWaitable.wait();
889 
890         compareImageWithGoldenPng(
891             target.get(),
892             GetTestDataPath("256x256_golden_multiple_targets_" + std::to_string(i) + ".png"),
893             kDefaultSaveImageIfComparisonFailed);
894     }
895 }
896 
TEST_F(CompositorVkTest,MultipleLayers)897 TEST_F(CompositorVkTest, MultipleLayers) {
898     auto compositor = createCompositor();
899     ASSERT_NE(compositor, nullptr);
900 
901     auto source1 = createSourceImageFromPng(GetTestDataPath("256x256_android.png"));
902     ASSERT_NE(source1, nullptr);
903 
904     auto source2 =
905         createSourceImageFromPng(GetTestDataPath("256x256_android_with_transparency.png"));
906     ASSERT_NE(source2, nullptr);
907 
908     auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
909                                       m_vkCommandPool, kDefaultImageWidth, kDefaultImageHeight);
910     ASSERT_NE(target, nullptr);
911     fillImageWith(target.get(), kColorBlack);
912 
913     Compositor::CompositionRequest compositionRequest = {
914         .target = createBorrowedImageInfo(target.get()),
915     };
916     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
917         .source = createBorrowedImageInfo(source1.get()),
918         .props =
919             {
920                 .composeMode = HWC2_COMPOSITION_DEVICE,
921                 .displayFrame =
922                     {
923                         .left = 0,
924                         .top = 0,
925                         .right = static_cast<int>(target->m_width),
926                         .bottom = static_cast<int>(target->m_height),
927                     },
928                 .crop =
929                     {
930                         .left = 32.0,
931                         .top = 32.0,
932                         .right = static_cast<float>(source1->m_width) - 32.0f,
933                         .bottom = static_cast<float>(source1->m_height) - 32.0f,
934                     },
935                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
936                 .alpha = 1.0,
937                 .color =
938                     {
939                         .r = 0,
940                         .g = 0,
941                         .b = 0,
942                         .a = 0,
943                     },
944                 .transform = HWC_TRANSFORM_NONE,
945             },
946     });
947     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
948         .source = createBorrowedImageInfo(source2.get()),
949         .props =
950             {
951                 .composeMode = HWC2_COMPOSITION_DEVICE,
952                 .displayFrame =
953                     {
954                         .left = 0,
955                         .top = 0,
956                         .right = static_cast<int>(target->m_width),
957                         .bottom = static_cast<int>(target->m_height),
958                     },
959                 .crop =
960                     {
961                         .left = 0,
962                         .top = 0,
963                         .right = static_cast<float>(source2->m_width) / 2.0f,
964                         .bottom = static_cast<float>(source2->m_height) / 2.0f,
965                     },
966                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
967                 .alpha = 1.0,
968                 .color =
969                     {
970                         .r = 0,
971                         .g = 0,
972                         .b = 0,
973                         .a = 0,
974                     },
975                 .transform = HWC_TRANSFORM_ROT_90,
976             },
977     });
978     compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{
979         .source = createBorrowedImageInfo(source2.get()),
980         .props =
981             {
982                 .composeMode = HWC2_COMPOSITION_DEVICE,
983                 .displayFrame =
984                     {
985                         .left = 0,
986                         .top = 0,
987                         .right = static_cast<int>(target->m_width) / 2,
988                         .bottom = static_cast<int>(target->m_height),
989                     },
990                 .crop =
991                     {
992                         .left = 0,
993                         .top = 0,
994                         .right = static_cast<float>(source2->m_width),
995                         .bottom = static_cast<float>(source2->m_height),
996                     },
997                 .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED,
998                 .alpha = 1.0,
999                 .color =
1000                     {
1001                         .r = 0,
1002                         .g = 0,
1003                         .b = 0,
1004                         .a = 0,
1005                     },
1006                 .transform = HWC_TRANSFORM_FLIP_V_ROT_90,
1007             },
1008     });
1009 
1010     auto compositionCompleteWaitable = compositor->compose(compositionRequest);
1011     compositionCompleteWaitable.wait();
1012 
1013     compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_multiple_layers.png"),
1014                               kDefaultSaveImageIfComparisonFailed);
1015 }
1016 
1017 }  // namespace
1018 }  // namespace vk
1019 }  // namespace gfxstream
1020