| #include <gtest/gtest.h> |
| |
| #include "CompositorVk.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <filesystem> |
| #include <glm/gtx/matrix_transform_2d.hpp> |
| #include <memory> |
| #include <optional> |
| |
| #include "BorrowedImageVk.h" |
| #include "aemu/base/synchronization/Lock.h" |
| #include "tests/ImageUtils.h" |
| #include "tests/VkTestUtils.h" |
| #include "vulkan/VulkanDispatch.h" |
| #include "vulkan/vk_util.h" |
| |
| namespace gfxstream { |
| namespace vk { |
| namespace { |
| |
| static constexpr const bool kDefaultSaveImageIfComparisonFailed = false; |
| |
| std::string GetTestDataPath(const std::string& basename) { |
| const std::filesystem::path currentPath = android::base::getProgramDirectory(); |
| return (currentPath / "tests" / "testdata" / basename).string(); |
| } |
| |
| static constexpr const uint32_t kColorBlack = 0xFF000000; |
| static constexpr const uint32_t kColorRed = 0xFF0000FF; |
| static constexpr const uint32_t kColorGreen = 0xFF00FF00; |
| static constexpr const uint32_t kDefaultImageWidth = 256; |
| static constexpr const uint32_t kDefaultImageHeight = 256; |
| |
| class CompositorVkTest : public ::testing::Test { |
| protected: |
| using TargetImage = RenderResourceVk<VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT>; |
| using SourceImage = RenderTextureVk; |
| |
| static void SetUpTestCase() { k_vk = vkDispatch(false); } |
| |
| void SetUp() override { |
| #if defined(__APPLE__) && defined(__arm64__) |
| GTEST_SKIP() << "Skipping all test on Apple M2, as they are failing, see b/263494782"; |
| #endif |
| ASSERT_NE(k_vk, nullptr); |
| createInstance(); |
| pickPhysicalDevice(); |
| createLogicalDevice(); |
| |
| if (!supportsFeatures(TargetImage::k_vkFormat, VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) { |
| GTEST_SKIP() << "Skipping test as format " << TargetImage::k_vkFormat |
| << " does not support VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT"; |
| } |
| if (!supportsFeatures(SourceImage::k_vkFormat, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) { |
| GTEST_SKIP() << "Skipping test as format " << SourceImage::k_vkFormat |
| << " does not support VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT"; |
| } |
| |
| const VkCommandPoolCreateInfo commandPoolCi = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
| .queueFamilyIndex = m_compositorQueueFamilyIndex, |
| }; |
| ASSERT_EQ(k_vk->vkCreateCommandPool(m_vkDevice, &commandPoolCi, nullptr, &m_vkCommandPool), |
| VK_SUCCESS); |
| |
| k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0, &m_compositorVkQueue); |
| ASSERT_NE(m_compositorVkQueue, VK_NULL_HANDLE); |
| |
| m_compositorVkQueueLock = std::make_shared<android::base::Lock>(); |
| } |
| |
| void TearDown() override { |
| #if defined(__APPLE__) && defined(__arm64__) |
| return; |
| #endif |
| |
| k_vk->vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr); |
| k_vk->vkDestroyDevice(m_vkDevice, nullptr); |
| m_vkDevice = VK_NULL_HANDLE; |
| k_vk->vkDestroyInstance(m_vkInstance, nullptr); |
| m_vkInstance = VK_NULL_HANDLE; |
| } |
| |
| std::unique_ptr<CompositorVk> createCompositor() { |
| return CompositorVk::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_compositorVkQueueLock, m_compositorQueueFamilyIndex, |
| /*maxFramesInFlight=*/3); |
| } |
| |
| template <typename SourceOrTargetImage> |
| std::unique_ptr<const SourceOrTargetImage> createImageWithColor(uint32_t sourceWidth, |
| uint32_t sourceHeight, |
| uint32_t sourceColor) { |
| auto source = |
| SourceOrTargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, sourceWidth, sourceHeight); |
| if (source == nullptr) { |
| return nullptr; |
| } |
| |
| std::vector<uint32_t> sourcePixels(sourceWidth * sourceHeight, sourceColor); |
| if (!source->write(sourcePixels)) { |
| return nullptr; |
| } |
| |
| return source; |
| } |
| |
| std::unique_ptr<const SourceImage> createSourceImageFromPng(const std::string& filename) { |
| uint32_t sourceWidth; |
| uint32_t sourceHeight; |
| std::vector<uint32_t> sourcePixels; |
| if (!LoadRGBAFromPng(filename, &sourceWidth, &sourceHeight, &sourcePixels)) { |
| return nullptr; |
| } |
| |
| auto source = |
| SourceImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, sourceWidth, sourceHeight); |
| if (source == nullptr) { |
| return nullptr; |
| } |
| |
| if (!source->write(sourcePixels)) { |
| return nullptr; |
| } |
| |
| return source; |
| } |
| |
| bool isRGBAPixelNear(uint32_t actualPixel, uint32_t expectedPixel) { |
| const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel); |
| const uint8_t* expectedRGBA = reinterpret_cast<const uint8_t*>(&expectedPixel); |
| |
| constexpr const uint32_t kRGBA8888Tolerance = 2; |
| for (uint32_t channel = 0; channel < 4; channel++) { |
| const uint8_t actualChannel = actualRGBA[channel]; |
| const uint8_t expectedChannel = expectedRGBA[channel]; |
| |
| if ((std::max(actualChannel, expectedChannel) - |
| std::min(actualChannel, expectedChannel)) > kRGBA8888Tolerance) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool compareRGBAPixels(const uint32_t* actualPixels, const uint32_t* expectedPixels, |
| const uint32_t width, const uint32_t height) { |
| bool comparisonFailed = false; |
| |
| uint32_t reportedIncorrectPixels = 0; |
| constexpr const uint32_t kMaxReportedIncorrectPixels = 10; |
| |
| for (uint32_t y = 0; y < width; y++) { |
| for (uint32_t x = 0; x < height; x++) { |
| const uint32_t actualPixel = actualPixels[y * height + x]; |
| const uint32_t expectedPixel = expectedPixels[y * width + x]; |
| if (!isRGBAPixelNear(actualPixel, expectedPixel)) { |
| comparisonFailed = true; |
| if (reportedIncorrectPixels < kMaxReportedIncorrectPixels) { |
| reportedIncorrectPixels++; |
| const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel); |
| const uint8_t* expectedRGBA = |
| reinterpret_cast<const uint8_t*>(&expectedPixel); |
| ADD_FAILURE() |
| << "Pixel comparison failed at (" << x << ", " << y << ") " |
| << " with actual " |
| << " r:" << static_cast<int>(actualRGBA[0]) |
| << " g:" << static_cast<int>(actualRGBA[1]) |
| << " b:" << static_cast<int>(actualRGBA[2]) |
| << " a:" << static_cast<int>(actualRGBA[3]) << " but expected " |
| << " r:" << static_cast<int>(expectedRGBA[0]) |
| << " g:" << static_cast<int>(expectedRGBA[1]) |
| << " b:" << static_cast<int>(expectedRGBA[2]) |
| << " a:" << static_cast<int>(expectedRGBA[3]); |
| } |
| } |
| } |
| } |
| return comparisonFailed; |
| } |
| |
| void compareImageWithGoldenPng(const TargetImage* target, const std::string& filename, |
| const bool saveImageIfComparisonFailed) { |
| const uint32_t targetWidth = target->m_width; |
| const uint32_t targetHeight = target->m_height; |
| const auto targetPixelsOpt = target->read(); |
| ASSERT_TRUE(targetPixelsOpt.has_value()); |
| const auto& targetPixels = *targetPixelsOpt; |
| |
| uint32_t goldenWidth; |
| uint32_t goldenHeight; |
| std::vector<uint32_t> goldenPixels; |
| const bool loadedGolden = |
| LoadRGBAFromPng(filename, &goldenWidth, &goldenHeight, &goldenPixels); |
| EXPECT_TRUE(loadedGolden) << "Failed to load golden image from " << filename; |
| |
| bool comparisonFailed = !loadedGolden; |
| |
| if (loadedGolden) { |
| EXPECT_EQ(target->m_width, goldenWidth) |
| << "Invalid width comparison with golden image from " << filename; |
| EXPECT_EQ(target->m_height, goldenHeight) |
| << "Invalid height comparison with golden image from " << filename; |
| if (targetWidth != goldenWidth || targetHeight != goldenHeight) { |
| comparisonFailed = true; |
| } |
| if (!comparisonFailed) { |
| comparisonFailed = compareRGBAPixels(targetPixels.data(), goldenPixels.data(), |
| goldenWidth, goldenHeight); |
| } |
| } |
| |
| if (saveImageIfComparisonFailed && comparisonFailed) { |
| const std::string output = (std::filesystem::temp_directory_path() / |
| std::filesystem::path(filename).filename()) |
| .string(); |
| SaveRGBAToPng(targetWidth, targetHeight, targetPixels.data(), output); |
| ADD_FAILURE() << "Saved composition result to " << output; |
| } |
| } |
| |
| template <typename SourceOrTargetImage> |
| std::unique_ptr<BorrowedImageInfoVk> createBorrowedImageInfo(const SourceOrTargetImage* image) { |
| static int sImageId = 0; |
| |
| auto ret = std::make_unique<BorrowedImageInfoVk>(); |
| ret->id = sImageId++; |
| ret->width = image->m_width; |
| ret->height = image->m_height; |
| ret->image = image->m_vkImage; |
| ret->imageCreateInfo = image->m_vkImageCreateInfo; |
| ret->imageView = image->m_vkImageView; |
| ret->preBorrowLayout = SourceOrTargetImage::k_vkImageLayout; |
| ret->preBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex; |
| ret->postBorrowLayout = SourceOrTargetImage::k_vkImageLayout; |
| ret->postBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex; |
| return ret; |
| } |
| |
| void checkImageFilledWith(const TargetImage* image, uint32_t expectedColor) { |
| auto actualPixelsOpt = image->read(); |
| ASSERT_TRUE(actualPixelsOpt.has_value()); |
| auto& actualPixels = *actualPixelsOpt; |
| |
| const std::vector<uint32_t> expectedPixels(image->numOfPixels(), expectedColor); |
| compareRGBAPixels(actualPixels.data(), expectedPixels.data(), image->m_width, |
| image->m_height); |
| } |
| |
| void fillImageWith(const TargetImage* image, uint32_t color) { |
| const std::vector<uint32_t> pixels(image->numOfPixels(), color); |
| ASSERT_TRUE(image->write(pixels)) << "Failed to fill image with color:" << color; |
| checkImageFilledWith(image, color); |
| } |
| |
| static const VulkanDispatch* k_vk; |
| VkInstance m_vkInstance = VK_NULL_HANDLE; |
| VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE; |
| uint32_t m_compositorQueueFamilyIndex = 0; |
| VkDevice m_vkDevice = VK_NULL_HANDLE; |
| VkCommandPool m_vkCommandPool = VK_NULL_HANDLE; |
| VkQueue m_compositorVkQueue = VK_NULL_HANDLE; |
| std::shared_ptr<android::base::Lock> m_compositorVkQueueLock; |
| |
| private: |
| void createInstance() { |
| const VkApplicationInfo appInfo = { |
| .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, |
| .pNext = nullptr, |
| .pApplicationName = "emulator CompositorVk unittest", |
| .applicationVersion = VK_MAKE_VERSION(1, 0, 0), |
| .pEngineName = "No Engine", |
| .engineVersion = VK_MAKE_VERSION(1, 0, 0), |
| .apiVersion = VK_API_VERSION_1_1, |
| }; |
| const VkInstanceCreateInfo instanceCi = { |
| .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
| .pApplicationInfo = &appInfo, |
| .enabledExtensionCount = 0, |
| .ppEnabledExtensionNames = nullptr, |
| }; |
| ASSERT_EQ(k_vk->vkCreateInstance(&instanceCi, nullptr, &m_vkInstance), VK_SUCCESS); |
| ASSERT_NE(m_vkInstance, VK_NULL_HANDLE); |
| } |
| |
| void pickPhysicalDevice() { |
| uint32_t physicalDeviceCount = 0; |
| ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, nullptr), |
| VK_SUCCESS); |
| ASSERT_GT(physicalDeviceCount, 0); |
| std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount); |
| ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, |
| physicalDevices.data()), |
| VK_SUCCESS); |
| for (const auto &device : physicalDevices) { |
| uint32_t queueFamilyCount = 0; |
| k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); |
| ASSERT_GT(queueFamilyCount, 0); |
| std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount); |
| k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, |
| queueFamilyProperties.data()); |
| uint32_t queueFamilyIndex = 0; |
| for (; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) { |
| if (CompositorVk::queueSupportsComposition( |
| queueFamilyProperties[queueFamilyIndex])) { |
| break; |
| } |
| } |
| if (queueFamilyIndex == queueFamilyCount) { |
| continue; |
| } |
| |
| m_compositorQueueFamilyIndex = queueFamilyIndex; |
| m_vkPhysicalDevice = device; |
| return; |
| } |
| FAIL() << "Can't find a suitable VkPhysicalDevice."; |
| } |
| |
| void createLogicalDevice() { |
| const float queuePriority = 1.0f; |
| const VkDeviceQueueCreateInfo queueCi = { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .queueFamilyIndex = m_compositorQueueFamilyIndex, |
| .queueCount = 1, |
| .pQueuePriorities = &queuePriority, |
| }; |
| const VkPhysicalDeviceFeatures2 features = { |
| .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, |
| .pNext = nullptr, |
| }; |
| const VkDeviceCreateInfo deviceCi = { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
| .pNext = &features, |
| .queueCreateInfoCount = 1, |
| .pQueueCreateInfos = &queueCi, |
| .enabledLayerCount = 0, |
| .enabledExtensionCount = 0, |
| .ppEnabledExtensionNames = nullptr, |
| }; |
| ASSERT_EQ(k_vk->vkCreateDevice(m_vkPhysicalDevice, &deviceCi, nullptr, &m_vkDevice), |
| VK_SUCCESS); |
| ASSERT_NE(m_vkDevice, VK_NULL_HANDLE); |
| } |
| |
| bool supportsFeatures(VkFormat format, VkFormatFeatureFlags features) { |
| VkFormatProperties formatProperties = {}; |
| k_vk->vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice, format, &formatProperties); |
| return (formatProperties.optimalTilingFeatures & features) == features; |
| } |
| }; |
| |
| const VulkanDispatch* CompositorVkTest::k_vk = nullptr; |
| |
| TEST_F(CompositorVkTest, QueueSupportsComposition) { |
| VkQueueFamilyProperties properties = {}; |
| |
| properties.queueFlags &= ~VK_QUEUE_GRAPHICS_BIT; |
| ASSERT_FALSE(CompositorVk::queueSupportsComposition(properties)); |
| |
| properties.queueFlags |= VK_QUEUE_GRAPHICS_BIT; |
| ASSERT_TRUE(CompositorVk::queueSupportsComposition(properties)); |
| } |
| |
| TEST_F(CompositorVkTest, Init) { ASSERT_NE(createCompositor(), nullptr); } |
| |
| TEST_F(CompositorVkTest, EmptyCompositionShouldDrawABlackFrame) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| std::vector<std::unique_ptr<const TargetImage>> targets; |
| constexpr const uint32_t kNumImages = 10; |
| for (uint32_t i = 0; i < kNumImages; i++) { |
| auto target = |
| TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, kDefaultImageWidth, kDefaultImageHeight); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorRed); |
| targets.emplace_back(std::move(target)); |
| } |
| |
| for (uint32_t i = 0; i < kNumImages; i++) { |
| const Compositor::CompositionRequest compositionRequest = { |
| .target = createBorrowedImageInfo(targets[i].get()), |
| .layers = {}, // Note: this is empty! |
| }; |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| } |
| |
| for (const auto& target : targets) { |
| checkImageFilledWith(target.get(), kColorBlack); |
| } |
| } |
| |
| TEST_F(CompositorVkTest, SimpleComposition) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png")); |
| ASSERT_NE(source, nullptr); |
| |
| auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, source->m_width, source->m_height); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorBlack); |
| |
| Compositor::CompositionRequest compositionRequest = { |
| .target = createBorrowedImageInfo(target.get()), |
| }; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 64, |
| .top = 32, |
| .right = 128, |
| .bottom = 160, |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source->m_width), |
| .bottom = static_cast<float>(source->m_height), |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng(target.get(), |
| GetTestDataPath("256x256_golden_simple_composition.png"), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| |
| TEST_F(CompositorVkTest, BlendPremultiplied) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| auto source = |
| createSourceImageFromPng(GetTestDataPath("256x256_android_with_transparency.png")); |
| ASSERT_NE(source, nullptr); |
| |
| auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, source->m_width, source->m_height); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorBlack); |
| |
| Compositor::CompositionRequest compositionRequest = { |
| .target = createBorrowedImageInfo(target.get()), |
| }; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<int>(target->m_width), |
| .bottom = static_cast<int>(target->m_height), |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source->m_width), |
| .bottom = static_cast<float>(source->m_height), |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng(target.get(), |
| GetTestDataPath("256x256_golden_blend_premultiplied.png"), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| |
| TEST_F(CompositorVkTest, Crop) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png")); |
| ASSERT_NE(source, nullptr); |
| |
| auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, source->m_width, source->m_height); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorBlack); |
| |
| Compositor::CompositionRequest compositionRequest = { |
| .target = createBorrowedImageInfo(target.get()), |
| }; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<int>(target->m_width), |
| .bottom = static_cast<int>(target->m_height), |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source->m_width) / 2.0f, |
| .bottom = static_cast<float>(source->m_height) / 2.0f, |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_crop.png"), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| |
| TEST_F(CompositorVkTest, Transformations) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| auto source = createSourceImageFromPng(GetTestDataPath("256x256_android.png")); |
| ASSERT_NE(source, nullptr); |
| |
| Compositor::CompositionRequest compositionRequest; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 32, |
| .top = 32, |
| .right = 224, |
| .bottom = 224, |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source->m_width), |
| .bottom = static_cast<float>(source->m_height), |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| |
| const std::unordered_map<hwc_transform_t, std::string> transformToGolden = { |
| {HWC_TRANSFORM_NONE, "256x256_golden_transform_none.png"}, |
| {HWC_TRANSFORM_FLIP_H, "256x256_golden_transform_fliph.png"}, |
| {HWC_TRANSFORM_FLIP_V, "256x256_golden_transform_flipv.png"}, |
| {HWC_TRANSFORM_ROT_90, "256x256_golden_transform_rot90.png"}, |
| {HWC_TRANSFORM_ROT_180, "256x256_golden_transform_rot180.png"}, |
| {HWC_TRANSFORM_ROT_270, "256x256_golden_transform_rot270.png"}, |
| {HWC_TRANSFORM_FLIP_H_ROT_90, "256x256_golden_transform_fliphrot90.png"}, |
| {HWC_TRANSFORM_FLIP_V_ROT_90, "256x256_golden_transform_flipvrot90.png"}, |
| }; |
| |
| for (const auto [transform, golden] : transformToGolden) { |
| auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, |
| m_compositorVkQueue, m_vkCommandPool, 256, 256); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorBlack); |
| |
| compositionRequest.target = createBorrowedImageInfo(target.get()); |
| compositionRequest.layers[0].props.transform = transform; |
| compositionRequest.layers[0].source = createBorrowedImageInfo(source.get()); |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng(target.get(), GetTestDataPath(golden), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| } |
| |
| TEST_F(CompositorVkTest, MultipleTargetsComposition) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| constexpr const uint32_t kNumCompostions = 10; |
| |
| auto source = createImageWithColor<SourceImage>(256, 256, kColorGreen); |
| ASSERT_NE(source, nullptr); |
| |
| std::vector<std::unique_ptr<const TargetImage>> targets; |
| for (uint32_t i = 0; i < kNumCompostions; i++) { |
| auto target = createImageWithColor<TargetImage>(256, 256, kColorBlack); |
| ASSERT_NE(target, nullptr); |
| targets.emplace_back(std::move(target)); |
| } |
| |
| Compositor::CompositionRequest compositionRequest = {}; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = 0, |
| .bottom = static_cast<int>(source->m_height), |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source->m_width), |
| .bottom = static_cast<float>(source->m_height), |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| |
| const uint32_t displayFrameWidth = 256 / kNumCompostions; |
| for (uint32_t i = 0; i < kNumCompostions; i++) { |
| const auto& target = targets[i]; |
| |
| compositionRequest.target = createBorrowedImageInfo(target.get()); |
| compositionRequest.layers[0].source = createBorrowedImageInfo(source.get()), |
| compositionRequest.layers[0].props.displayFrame.left = (i + 0) * displayFrameWidth; |
| compositionRequest.layers[0].props.displayFrame.right = (i + 1) * displayFrameWidth; |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng( |
| target.get(), |
| GetTestDataPath("256x256_golden_multiple_targets_" + std::to_string(i) + ".png"), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| } |
| |
| TEST_F(CompositorVkTest, MultipleLayers) { |
| auto compositor = createCompositor(); |
| ASSERT_NE(compositor, nullptr); |
| |
| auto source1 = createSourceImageFromPng(GetTestDataPath("256x256_android.png")); |
| ASSERT_NE(source1, nullptr); |
| |
| auto source2 = |
| createSourceImageFromPng(GetTestDataPath("256x256_android_with_transparency.png")); |
| ASSERT_NE(source2, nullptr); |
| |
| auto target = TargetImage::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, |
| m_vkCommandPool, kDefaultImageWidth, kDefaultImageHeight); |
| ASSERT_NE(target, nullptr); |
| fillImageWith(target.get(), kColorBlack); |
| |
| Compositor::CompositionRequest compositionRequest = { |
| .target = createBorrowedImageInfo(target.get()), |
| }; |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source1.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<int>(target->m_width), |
| .bottom = static_cast<int>(target->m_height), |
| }, |
| .crop = |
| { |
| .left = 32.0, |
| .top = 32.0, |
| .right = static_cast<float>(source1->m_width) - 32.0f, |
| .bottom = static_cast<float>(source1->m_height) - 32.0f, |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_NONE, |
| }, |
| }); |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source2.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<int>(target->m_width), |
| .bottom = static_cast<int>(target->m_height), |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source2->m_width) / 2.0f, |
| .bottom = static_cast<float>(source2->m_height) / 2.0f, |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_ROT_90, |
| }, |
| }); |
| compositionRequest.layers.emplace_back(Compositor::CompositionRequestLayer{ |
| .source = createBorrowedImageInfo(source2.get()), |
| .props = |
| { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .displayFrame = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<int>(target->m_width) / 2, |
| .bottom = static_cast<int>(target->m_height), |
| }, |
| .crop = |
| { |
| .left = 0, |
| .top = 0, |
| .right = static_cast<float>(source2->m_width), |
| .bottom = static_cast<float>(source2->m_height), |
| }, |
| .blendMode = HWC2_BLEND_MODE_PREMULTIPLIED, |
| .alpha = 1.0, |
| .color = |
| { |
| .r = 0, |
| .g = 0, |
| .b = 0, |
| .a = 0, |
| }, |
| .transform = HWC_TRANSFORM_FLIP_V_ROT_90, |
| }, |
| }); |
| |
| auto compositionCompleteWaitable = compositor->compose(compositionRequest); |
| compositionCompleteWaitable.wait(); |
| |
| compareImageWithGoldenPng(target.get(), GetTestDataPath("256x256_golden_multiple_layers.png"), |
| kDefaultSaveImageIfComparisonFailed); |
| } |
| |
| } // namespace |
| } // namespace vk |
| } // namespace gfxstream |