blob: 028826446d0ae826af608fc73662bdf790e882b4 [file] [log] [blame]
#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