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