#include #include "CompositorVk.h" #include #include #include #include #include "tests/VkTestUtils.h" #include "vulkan/VulkanDispatch.h" #include "vulkan/vk_util.h" class CompositorVkTest : public ::testing::Test { protected: using RenderTarget = emugl::RenderResourceVk; 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 &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 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> m_renderTargets; std::vector 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 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 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 enabledDeviceExtensions = CompositorVk::getRequiredDeviceExtensions(); VkDeviceCreateInfo deviceCi = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &features, .queueCreateInfoCount = 1, .pQueueCreateInfos = &queueCi, .enabledLayerCount = 0, .enabledExtensionCount = static_cast(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 pixels(k_renderTargetNumOfPixels); for (uint32_t i = 0; i < k_renderTargetNumOfPixels; i++) { uint8_t v = static_cast((i / 4) & 0xff); uint8_t *pixel = reinterpret_cast(&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 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(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(&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(&textureColor); textureColor_[0] = 0xff; textureColor_[1] = 0; textureColor_[2] = 0; textureColor_[3] = 0xff; std::vector pixels(textureWidth * textureHeight, textureColor); ASSERT_TRUE(texture->write(pixels)); auto compositor = createCompositor(); ASSERT_NE(compositor, nullptr); auto composition = std::make_unique( texture->m_vkImageView, sampler, textureWidth, textureHeight); auto transform = glm::translate(glm::mat3(1.0f), glm::vec2(static_cast(textureLeft), static_cast(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(&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 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(&textureColor); textureColor_[0] = 0xff; textureColor_[1] = 0; textureColor_[2] = 0; textureColor_[3] = 0xff; std::vector 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( texture->m_vkImageView, sampler, textureWidth, textureHeight); auto transform = glm::translate( glm::mat3(1.0f), glm::vec2(static_cast(pos.m_left), static_cast(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(&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); }