blob: 7d585bd29da253162e64aa44657c1b80735cbe40 [file] [log] [blame]
#include <gtest/gtest.h>
#include "CompositorVk.h"
#include <algorithm>
#include <array>
#include <glm/gtx/matrix_transform_2d.hpp>
#include <optional>
#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);
k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0,
&m_compositorVkQueue);
ASSERT_TRUE(m_compositorVkQueue != VK_NULL_HANDLE);
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());
}
void TearDown() override {
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,
RenderTarget::k_vkFormat, RenderTarget::k_vkImageLayout,
RenderTarget::k_vkImageLayout, k_renderTargetWidth,
k_renderTargetHeight, m_renderTargetImageViews, m_vkCommandPool);
}
VkSampler createSampler() {
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};
VkSampler res;
VK_CHECK(k_vk->vkCreateSampler(m_vkDevice, &samplerCi, nullptr, &res));
return res;
}
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;
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) {
VkPhysicalDeviceDescriptorIndexingFeaturesEXT descIndexingFeatures = {
.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT};
VkPhysicalDeviceFeatures2 features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &descIndexingFeatures};
k_vk->vkGetPhysicalDeviceFeatures2(device, &features);
if (!CompositorVk::validatePhysicalDeviceFeatures(features)) {
continue;
}
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};
VkPhysicalDeviceDescriptorIndexingFeaturesEXT descIndexingFeatures = {
.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT};
VkPhysicalDeviceFeatures2 features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &descIndexingFeatures};
ASSERT_TRUE(CompositorVk::enablePhysicalDeviceFeatures(features));
const std::vector<const char *> enabledDeviceExtensions =
CompositorVk::getRequiredDeviceExtensions();
VkDeviceCreateInfo deviceCi = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &features,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queueCi,
.enabledLayerCount = 0,
.enabledExtensionCount =
static_cast<uint32_t>(enabledDeviceExtensions.size()),
.ppEnabledExtensionNames = enabledDeviceExtensions.data()};
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, ValidatePhysicalDeviceFeatures) {
VkPhysicalDeviceFeatures2 features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
ASSERT_FALSE(CompositorVk::validatePhysicalDeviceFeatures(features));
VkPhysicalDeviceDescriptorIndexingFeaturesEXT descIndexingFeatures = {
.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
.pNext = &descIndexingFeatures};
ASSERT_FALSE(CompositorVk::validatePhysicalDeviceFeatures(features));
descIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
ASSERT_TRUE(CompositorVk::validatePhysicalDeviceFeatures(features));
}
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, EnablePhysicalDeviceFeatures) {
VkPhysicalDeviceFeatures2 features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
ASSERT_FALSE(CompositorVk::enablePhysicalDeviceFeatures(features));
VkPhysicalDeviceDescriptorIndexingFeaturesEXT descIndexingFeatures = {
.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
.pNext = &descIndexingFeatures};
ASSERT_TRUE(CompositorVk::enablePhysicalDeviceFeatures(features));
ASSERT_EQ(descIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind,
VK_TRUE);
}
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();
ASSERT_NE(compositor, nullptr);
// render to render targets with event index
std::vector<VkCommandBuffer> cmdBuffs = {};
for (uint32_t i = 0; i < k_numOfRenderTargets; i++) {
if (i % 2 == 0) {
cmdBuffs.emplace_back(compositor->getCommandBuffer(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 sampler = createSampler();
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 composition = std::make_unique<Composition>(
texture->m_vkImageView, sampler, textureWidth, textureHeight);
auto transform = glm::translate(glm::mat3(1.0f),
glm::vec2(static_cast<float>(textureLeft),
static_cast<float>(textureTop)));
composition->m_transform = transform;
compositor->setComposition(0, std::move(composition));
VkCommandBuffer cmdBuff = compositor->getCommandBuffer(0);
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);
}
}
}
k_vk->vkDestroySampler(m_vkDevice, sampler, nullptr);
}
TEST_F(CompositorVkTest, CompositingWithDifferentCompositionOnMultipleTargets) {
constexpr uint32_t textureWidth = 20;
constexpr uint32_t textureHeight = 30;
struct Rect {
uint32_t m_top;
uint32_t m_bottom;
uint32_t m_left;
uint32_t m_right;
};
std::vector<Rect> texturePositions(k_numOfRenderTargets);
for (int i = 0; i < k_numOfRenderTargets; i++) {
auto left = (i * 30) % (k_renderTargetWidth - textureWidth);
auto top = (i * 20) % (k_renderTargetHeight - textureHeight);
texturePositions[i].m_top = top;
texturePositions[i].m_bottom = top + textureHeight;
texturePositions[i].m_left = left;
texturePositions[i].m_right = left + textureWidth;
}
auto sampler = createSampler();
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);
for (int i = 0; i < k_numOfRenderTargets; i++) {
const auto &pos = texturePositions[i];
auto composition = std::make_unique<Composition>(
texture->m_vkImageView, sampler, textureWidth, textureHeight);
auto transform = glm::translate(
glm::mat3(1.0f), glm::vec2(static_cast<float>(pos.m_left),
static_cast<float>(pos.m_top)));
composition->m_transform = transform;
compositor->setComposition(i, std::move(composition));
VkCommandBuffer cmdBuff = compositor->getCommandBuffer(i);
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 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 >= pos.m_top && i < pos.m_bottom && j >= pos.m_left &&
j < pos.m_right) {
EXPECT_EQ(pixel[0], 0xff);
} else {
EXPECT_EQ(pixel[0], 0);
}
}
}
}
k_vk->vkDestroySampler(m_vkDevice, sampler, nullptr);
}