blob: 8f623f9a8a3a7f7f045f9cfbee70d28229230a01 [file] [log] [blame]
#include <gtest/gtest.h>
#include "CompositorVk.h"
#include <algorithm>
#include <array>
#include <glm/gtx/matrix_transform_2d.hpp>
#include <memory>
#include <optional>
#include "base/Lock.h"
#include "tests/VkTestUtils.h"
#include "vulkan/VulkanDispatch.h"
#include "vulkan/vk_util.h"
class CompositorVkTest : public ::testing::Test {
protected:
using RenderTarget = emugl::RenderResourceVk<VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT>;
using RenderTexture = emugl::RenderTextureVk;
static void SetUpTestCase() { k_vk = emugl::vkDispatch(false); }
static constexpr uint32_t k_numOfRenderTargets = 10;
static constexpr uint32_t k_renderTargetWidth = 255;
static constexpr uint32_t k_renderTargetHeight = 255;
static constexpr uint32_t k_renderTargetNumOfPixels =
k_renderTargetWidth * k_renderTargetHeight;
void SetUp() override {
ASSERT_NE(k_vk, nullptr);
createInstance();
pickPhysicalDevice();
createLogicalDevice();
VkFormatProperties formatProperties;
k_vk->vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice, RenderTarget::k_vkFormat,
&formatProperties);
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) {
GTEST_SKIP();
}
k_vk->vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice, RenderTexture::k_vkFormat,
&formatProperties);
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
GTEST_SKIP();
}
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);
VkCommandBufferAllocateInfo cmdBuffAllocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_vkCommandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = k_numOfRenderTargets};
m_vkCommandBuffers.resize(k_numOfRenderTargets);
VK_CHECK(k_vk->vkAllocateCommandBuffers(m_vkDevice, &cmdBuffAllocInfo,
m_vkCommandBuffers.data()));
k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0, &m_compositorVkQueue);
ASSERT_TRUE(m_compositorVkQueue != VK_NULL_HANDLE);
m_compositorVkQueueLock = std::make_shared<android::base::Lock>();
for (uint32_t i = 0; i < k_numOfRenderTargets; i++) {
auto renderTarget =
RenderTarget::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
m_vkCommandPool, k_renderTargetHeight, k_renderTargetWidth);
ASSERT_NE(renderTarget, nullptr);
m_renderTargets.emplace_back(std::move(renderTarget));
}
m_renderTargetImageViews.resize(m_renderTargets.size());
ASSERT_EQ(std::transform(m_renderTargets.begin(), m_renderTargets.end(),
m_renderTargetImageViews.begin(),
[](const std::unique_ptr<const RenderTarget> &renderTarget) {
return renderTarget->m_vkImageView;
}),
m_renderTargetImageViews.end());
setUpRGBASampler();
}
void TearDown() override {
k_vk->vkDestroySampler(m_vkDevice, m_rgbaVkSampler, nullptr);
k_vk->vkFreeCommandBuffers(m_vkDevice, m_vkCommandPool, m_vkCommandBuffers.size(),
m_vkCommandBuffers.data());
m_vkCommandBuffers.clear();
m_renderTargets.clear();
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,
RenderTarget::k_vkFormat, RenderTarget::k_vkImageLayout, RenderTarget::k_vkImageLayout,
m_renderTargetImageViews.size(), m_vkCommandPool, m_rgbaVkSampler);
}
std::vector<std::unique_ptr<CompositorVkRenderTarget>> createCompositorRenderTargets(
CompositorVk &compositor) {
std::vector<std::unique_ptr<CompositorVkRenderTarget>> res;
for (VkImageView imageView : m_renderTargetImageViews) {
res.emplace_back(compositor.createRenderTarget(imageView, k_renderTargetWidth,
k_renderTargetHeight));
}
return res;
}
void setUpRGBASampler() {
VkSamplerCreateInfo samplerCi = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_NEAREST,
.minFilter = VK_FILTER_NEAREST,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = VK_FALSE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.minLod = 0.0f,
.maxLod = 0.0f,
.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK,
.unnormalizedCoordinates = VK_FALSE};
VK_CHECK(k_vk->vkCreateSampler(m_vkDevice, &samplerCi, nullptr, &m_rgbaVkSampler));
}
static const goldfish_vk::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;
std::vector<std::unique_ptr<const RenderTarget>> m_renderTargets;
std::vector<VkImageView> m_renderTargetImageViews;
VkCommandPool m_vkCommandPool = VK_NULL_HANDLE;
VkQueue m_compositorVkQueue = VK_NULL_HANDLE;
std::shared_ptr<android::base::Lock> m_compositorVkQueueLock;
std::vector<VkCommandBuffer> m_vkCommandBuffers;
VkSampler m_rgbaVkSampler = VK_NULL_HANDLE;
private:
void createInstance() {
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};
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_TRUE(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::validateQueueFamilyProperties(
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;
VkDeviceQueueCreateInfo queueCi = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = m_compositorQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority};
VkPhysicalDeviceFeatures2 features = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = nullptr};
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_TRUE(m_vkDevice != VK_NULL_HANDLE);
}
};
const goldfish_vk::VulkanDispatch *CompositorVkTest::k_vk = nullptr;
TEST_F(CompositorVkTest, Init) { ASSERT_NE(createCompositor(), nullptr); }
TEST_F(CompositorVkTest, ValidateQueueFamilyProperties) {
VkQueueFamilyProperties properties = {};
properties.queueFlags &= ~VK_QUEUE_GRAPHICS_BIT;
ASSERT_FALSE(CompositorVk::validateQueueFamilyProperties(properties));
properties.queueFlags |= VK_QUEUE_GRAPHICS_BIT;
ASSERT_TRUE(CompositorVk::validateQueueFamilyProperties(properties));
}
TEST_F(CompositorVkTest, EmptyCompositionShouldDrawABlackFrame) {
std::vector<uint32_t> pixels(k_renderTargetNumOfPixels);
for (uint32_t i = 0; i < k_renderTargetNumOfPixels; i++) {
uint8_t v = static_cast<uint8_t>((i / 4) & 0xff);
uint8_t *pixel = reinterpret_cast<uint8_t *>(&pixels[i]);
pixel[0] = v;
pixel[1] = v;
pixel[2] = v;
pixel[3] = 0xff;
}
for (uint32_t i = 0; i < k_numOfRenderTargets; i++) {
ASSERT_TRUE(m_renderTargets[i]->write(pixels));
auto maybeImageBytes = m_renderTargets[i]->read();
ASSERT_TRUE(maybeImageBytes.has_value());
for (uint32_t i = 0; i < k_renderTargetNumOfPixels; i++) {
ASSERT_EQ(pixels[i], maybeImageBytes.value()[i]);
}
}
auto compositor = createCompositor();
auto renderTargets = createCompositorRenderTargets(*compositor);
ASSERT_NE(compositor, nullptr);
// render to render targets with event index
std::vector<VkCommandBuffer> cmdBuffs = {};
for (uint32_t i = 0; i < k_numOfRenderTargets; i++) {
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_CHECK(k_vk->vkBeginCommandBuffer(m_vkCommandBuffers[i], &beginInfo));
compositor->recordCommandBuffers(i, m_vkCommandBuffers[i], *renderTargets[i]);
VK_CHECK(k_vk->vkEndCommandBuffer(m_vkCommandBuffers[i]));
if (i % 2 == 0) {
cmdBuffs.emplace_back(m_vkCommandBuffers[i]);
}
}
VkSubmitInfo submitInfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = static_cast<uint32_t>(cmdBuffs.size()),
.pCommandBuffers = cmdBuffs.data()};
ASSERT_EQ(k_vk->vkQueueSubmit(m_compositorVkQueue, 1, &submitInfo, VK_NULL_HANDLE), VK_SUCCESS);
ASSERT_EQ(k_vk->vkQueueWaitIdle(m_compositorVkQueue), VK_SUCCESS);
for (uint32_t i = 0; i < k_numOfRenderTargets; i++) {
auto maybeImagePixels = m_renderTargets[i]->read();
ASSERT_TRUE(maybeImagePixels.has_value());
const auto &imagePixels = maybeImagePixels.value();
for (uint32_t j = 0; j < k_renderTargetNumOfPixels; j++) {
const auto pixel = reinterpret_cast<const uint8_t *>(&imagePixels[j]);
// should only render to render targets with even index
if (i % 2 == 0) {
ASSERT_EQ(pixel[0], 0);
ASSERT_EQ(pixel[1], 0);
ASSERT_EQ(pixel[2], 0);
ASSERT_EQ(pixel[3], 0xff);
} else {
ASSERT_EQ(pixels[j], imagePixels[j]);
}
}
}
}
TEST_F(CompositorVkTest, SimpleComposition) {
constexpr uint32_t textureLeft = 30;
constexpr uint32_t textureRight = 50;
constexpr uint32_t textureTop = 10;
constexpr uint32_t textureBottom = 40;
constexpr uint32_t textureWidth = textureRight - textureLeft;
constexpr uint32_t textureHeight = textureBottom - textureTop;
auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
m_vkCommandPool, textureWidth, textureHeight);
uint32_t textureColor;
uint8_t *textureColor_ = reinterpret_cast<uint8_t *>(&textureColor);
textureColor_[0] = 0xff;
textureColor_[1] = 0;
textureColor_[2] = 0;
textureColor_[3] = 0xff;
std::vector<uint32_t> pixels(textureWidth * textureHeight, textureColor);
ASSERT_TRUE(texture->write(pixels));
auto compositor = createCompositor();
ASSERT_NE(compositor, nullptr);
ComposeLayer composeLayer = {
0 /* No color buffer handle */,
HWC2_COMPOSITION_DEVICE,
{
/* frame in which the texture shows up on the display */
textureLeft,
textureTop,
textureRight,
textureBottom,
},
{
/* how much of the texture itself to show */
0.0,
0.0,
static_cast<float>(textureWidth),
static_cast<float>(textureHeight),
},
HWC2_BLEND_MODE_PREMULTIPLIED /* blend mode */,
1.0 /* alpha */,
{0, 0, 0, 0} /* color (N/A) */,
HWC_TRANSFORM_NONE /* transform (no rotation */,
};
std::unique_ptr<ComposeLayerVk> composeLayerVkPtr = ComposeLayerVk::createFromHwc2ComposeLayer(
m_rgbaVkSampler, texture->m_vkImageView, composeLayer, textureWidth, textureHeight,
k_renderTargetWidth, k_renderTargetHeight);
std::vector<std::unique_ptr<ComposeLayerVk>> layers;
layers.emplace_back(std::move(composeLayerVkPtr));
auto composition = std::make_unique<Composition>(std::move(layers));
auto renderTargets = createCompositorRenderTargets(*compositor);
compositor->setComposition(0, std::move(composition));
VkCommandBuffer cmdBuff = m_vkCommandBuffers[0];
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_CHECK(k_vk->vkBeginCommandBuffer(cmdBuff, &beginInfo));
compositor->recordCommandBuffers(0, cmdBuff, *renderTargets[0]);
VK_CHECK(k_vk->vkEndCommandBuffer(cmdBuff));
VkSubmitInfo submitInfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &cmdBuff};
ASSERT_EQ(k_vk->vkQueueSubmit(m_compositorVkQueue, 1, &submitInfo, VK_NULL_HANDLE), VK_SUCCESS);
ASSERT_EQ(k_vk->vkQueueWaitIdle(m_compositorVkQueue), VK_SUCCESS);
auto maybeImagePixels = m_renderTargets[0]->read();
ASSERT_TRUE(maybeImagePixels.has_value());
const auto &imagePixels = maybeImagePixels.value();
for (uint32_t i = 0; i < k_renderTargetHeight; i++) {
for (uint32_t j = 0; j < k_renderTargetWidth; j++) {
uint32_t offset = i * k_renderTargetWidth + j;
const uint8_t *pixel = reinterpret_cast<const uint8_t *>(&imagePixels[offset]);
EXPECT_EQ(pixel[1], 0);
EXPECT_EQ(pixel[2], 0);
EXPECT_EQ(pixel[3], 0xff);
if (i >= textureTop && i < textureBottom && j >= textureLeft && j < textureRight) {
EXPECT_EQ(pixel[0], 0xff);
} else {
EXPECT_EQ(pixel[0], 0);
}
}
}
}
TEST_F(CompositorVkTest, CompositingWithDifferentCompositionOnMultipleTargets) {
constexpr uint32_t textureWidth = 20;
constexpr uint32_t textureHeight = 30;
ComposeLayer defaultComposeLayer = {
0 /* No color buffer handle */,
HWC2_COMPOSITION_DEVICE,
{
/* display frame (to be replaced for each of k_numOfRenderTargets */
0,
0,
0,
0,
},
{
/* how much of the texture itself to show */
0.0,
0.0,
static_cast<float>(textureWidth),
static_cast<float>(textureHeight),
},
HWC2_BLEND_MODE_PREMULTIPLIED /* blend mode */,
1.0 /* alpha */,
{0, 0, 0, 0} /* color (N/A) */,
HWC_TRANSFORM_NONE /* transform (no rotation */,
};
std::vector<ComposeLayer> composeLayers(k_numOfRenderTargets);
for (int i = 0; i < k_numOfRenderTargets; i++) {
composeLayers[i] = defaultComposeLayer;
int left = (i * 30) % (k_renderTargetWidth - textureWidth);
int top = (i * 20) % (k_renderTargetHeight - textureHeight);
int right = left + textureWidth;
int bottom = top + textureHeight;
composeLayers[i].displayFrame = {
left,
top,
right,
bottom,
};
}
auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
m_vkCommandPool, textureWidth, textureHeight);
uint32_t textureColor;
uint8_t *textureColor_ = reinterpret_cast<uint8_t *>(&textureColor);
textureColor_[0] = 0xff;
textureColor_[1] = 0;
textureColor_[2] = 0;
textureColor_[3] = 0xff;
std::vector<uint32_t> pixels(textureWidth * textureHeight, textureColor);
ASSERT_TRUE(texture->write(pixels));
auto compositor = createCompositor();
ASSERT_NE(compositor, nullptr);
auto renderTargets = createCompositorRenderTargets(*compositor);
for (int i = 0; i < k_numOfRenderTargets; i++) {
std::unique_ptr<ComposeLayerVk> composeLayerVkPtr =
ComposeLayerVk::createFromHwc2ComposeLayer(
m_rgbaVkSampler, texture->m_vkImageView, composeLayers[i], textureWidth,
textureHeight, k_renderTargetWidth, k_renderTargetHeight);
std::vector<std::unique_ptr<ComposeLayerVk>> layers;
layers.emplace_back(std::move(composeLayerVkPtr));
auto composition = std::make_unique<Composition>(std::move(layers));
compositor->setComposition(i, std::move(composition));
VkCommandBuffer cmdBuff = m_vkCommandBuffers[i];
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_CHECK(k_vk->vkBeginCommandBuffer(cmdBuff, &beginInfo));
compositor->recordCommandBuffers(i, cmdBuff, *renderTargets[i]);
VK_CHECK(k_vk->vkEndCommandBuffer(cmdBuff));
VkSubmitInfo submitInfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &cmdBuff};
ASSERT_EQ(k_vk->vkQueueSubmit(m_compositorVkQueue, 1, &submitInfo, VK_NULL_HANDLE),
VK_SUCCESS);
ASSERT_EQ(k_vk->vkQueueWaitIdle(m_compositorVkQueue), VK_SUCCESS);
auto maybeImagePixels = m_renderTargets[i]->read();
ASSERT_TRUE(maybeImagePixels.has_value());
const auto &imagePixels = maybeImagePixels.value();
for (uint32_t j = 0; j < k_renderTargetHeight; j++) {
for (uint32_t k = 0; k < k_renderTargetWidth; k++) {
uint32_t offset = j * k_renderTargetWidth + k;
const uint8_t *pixel = reinterpret_cast<const uint8_t *>(&imagePixels[offset]);
EXPECT_EQ(pixel[1], 0);
EXPECT_EQ(pixel[2], 0);
EXPECT_EQ(pixel[3], 0xff);
if (j >= composeLayers[i].displayFrame.top &&
j < composeLayers[i].displayFrame.bottom &&
k >= composeLayers[i].displayFrame.left &&
k < composeLayers[i].displayFrame.right) {
EXPECT_EQ(pixel[0], 0xff);
} else {
EXPECT_EQ(pixel[0], 0);
}
}
}
}
}