// Note: needs to be included before DisplayVk to avoid conflicts // between gtest and x11 headers. #include #include "DisplayVk.h" #include "BorrowedImageVk.h" #include "Standalone.h" #include "aemu/base/synchronization/Lock.h" #include "tests/VkTestUtils.h" #include "vulkan/VulkanDispatch.h" using gfxstream::DisplaySurface; namespace gfxstream { namespace vk { namespace { class DisplayVkTest : public ::testing::Test { protected: using RenderTexture = RenderTextureVk; static void SetUpTestCase() { k_vk = vkDispatch(false); } void SetUp() override { // skip the test when testing without a window if (!shouldUseWindow()) { GTEST_SKIP(); } ASSERT_NE(k_vk, nullptr); createInstance(); createWindowAndSurface(); m_window = createOrGetTestWindow(0, 0, k_width, k_height); pickPhysicalDevice(); createLogicalDevice(); k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0, &m_compositorVkQueue); m_compositorVkQueueLock = std::make_shared(); k_vk->vkGetDeviceQueue(m_vkDevice, m_swapChainQueueFamilyIndex, 0, &m_swapChainVkQueue); m_swapChainVkQueueLock = std::make_shared(); 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); m_displayVk = std::make_unique( *k_vk, m_vkPhysicalDevice, m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex, m_vkDevice, m_compositorVkQueue, m_compositorVkQueueLock, m_swapChainVkQueue, m_swapChainVkQueueLock); m_displaySurface = std::make_unique( k_width, k_height, DisplaySurfaceVk::create(*k_vk, m_vkInstance, m_window->getNativeWindow())); ASSERT_NE(m_displaySurface, nullptr); m_displayVk->bindToSurface(m_displaySurface.get()); } void TearDown() override { if (shouldUseWindow()) { ASSERT_EQ(k_vk->vkQueueWaitIdle(m_compositorVkQueue), VK_SUCCESS); ASSERT_EQ(k_vk->vkQueueWaitIdle(m_swapChainVkQueue), VK_SUCCESS); m_displayVk.reset(); k_vk->vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr); k_vk->vkDestroyDevice(m_vkDevice, nullptr); k_vk->vkDestroySurfaceKHR(m_vkInstance, m_vkSurface, nullptr); k_vk->vkDestroyInstance(m_vkInstance, nullptr); } } std::unique_ptr createBorrowedImageInfo( const std::unique_ptr& texture) { static uint32_t sTextureId = 0; auto info = std::make_unique(); info->id = sTextureId++; info->width = texture->m_vkImageCreateInfo.extent.width; info->height = texture->m_vkImageCreateInfo.extent.height; info->image = texture->m_vkImage; info->imageCreateInfo = texture->m_vkImageCreateInfo; info->preBorrowLayout = RenderTexture::k_vkImageLayout; info->preBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex; info->postBorrowLayout = RenderTexture::k_vkImageLayout; info->postBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex; return info; } static const VulkanDispatch* k_vk; static constexpr uint32_t k_width = 0x100; static constexpr uint32_t k_height = 0x100; OSWindow *m_window; VkInstance m_vkInstance = VK_NULL_HANDLE; VkSurfaceKHR m_vkSurface = VK_NULL_HANDLE; VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE; uint32_t m_swapChainQueueFamilyIndex = 0; uint32_t m_compositorQueueFamilyIndex = 0; VkDevice m_vkDevice = VK_NULL_HANDLE; VkQueue m_compositorVkQueue = VK_NULL_HANDLE; std::shared_ptr m_compositorVkQueueLock; VkQueue m_swapChainVkQueue = VK_NULL_HANDLE; std::shared_ptr m_swapChainVkQueueLock; VkCommandPool m_vkCommandPool = VK_NULL_HANDLE; std::unique_ptr m_displayVk = nullptr; std::unique_ptr m_displaySurface = nullptr; private: void createInstance() { VkApplicationInfo appInfo = {.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "emulator SwapChainStateVk unittest", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_1}; auto extensions = SwapChainStateVk::getRequiredInstanceExtensions(); VkInstanceCreateInfo instanceCi = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledExtensionCount = static_cast(extensions.size()), .ppEnabledExtensionNames = extensions.data()}; ASSERT_EQ(k_vk->vkCreateInstance(&instanceCi, nullptr, &m_vkInstance), VK_SUCCESS); ASSERT_TRUE(m_vkInstance != VK_NULL_HANDLE); } void createWindowAndSurface() { m_window = createOrGetTestWindow(0, 0, k_width, k_height); ASSERT_NE(m_window, nullptr); // TODO(kaiyili, b/179477624): add support for other platforms #ifdef _WIN32 VkWin32SurfaceCreateInfoKHR surfaceCi = { .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, .hinstance = GetModuleHandle(nullptr), .hwnd = m_window->getNativeWindow()}; ASSERT_EQ(k_vk->vkCreateWin32SurfaceKHR(m_vkInstance, &surfaceCi, nullptr, &m_vkSurface), VK_SUCCESS); #endif } 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) { uint32_t queueFamilyCount = 0; k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); ASSERT_GT(queueFamilyCount, 0); std::vector queueProps(queueFamilyCount); k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueProps.data()); std::optional maybeSwapChainQueueFamilyIndex = std::nullopt; std::optional maybeCompositorQueueFamilyIndex = std::nullopt; for (uint32_t queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) { if (!maybeSwapChainQueueFamilyIndex.has_value() && SwapChainStateVk::validateQueueFamilyProperties(*k_vk, device, m_vkSurface, queueFamilyIndex) && SwapChainStateVk::createSwapChainCi(*k_vk, m_vkSurface, device, k_width, k_height, {queueFamilyIndex})) { maybeSwapChainQueueFamilyIndex = queueFamilyIndex; } if (!maybeCompositorQueueFamilyIndex.has_value() && CompositorVk::queueSupportsComposition(queueProps[queueFamilyIndex])) { maybeCompositorQueueFamilyIndex = queueFamilyIndex; } } if (!maybeSwapChainQueueFamilyIndex.has_value() || !maybeCompositorQueueFamilyIndex.has_value()) { continue; } m_swapChainQueueFamilyIndex = maybeSwapChainQueueFamilyIndex.value(); m_compositorQueueFamilyIndex = maybeCompositorQueueFamilyIndex.value(); m_vkPhysicalDevice = device; return; } FAIL() << "Can't find a suitable VkPhysicalDevice."; } void createLogicalDevice() { const float queuePriority = 1.0f; std::vector queueCis(0); for (auto queueFamilyIndex : std::unordered_set( {m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex})) { VkDeviceQueueCreateInfo queueCi = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = queueFamilyIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; queueCis.push_back(queueCi); } VkPhysicalDeviceFeatures2 features = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = nullptr}; auto extensions = SwapChainStateVk::getRequiredDeviceExtensions(); VkDeviceCreateInfo deviceCi = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &features, .queueCreateInfoCount = static_cast(queueCis.size()), .pQueueCreateInfos = queueCis.data(), .enabledLayerCount = 0, .enabledExtensionCount = static_cast(extensions.size()), .ppEnabledExtensionNames = extensions.data(), .pEnabledFeatures = nullptr}; ASSERT_EQ(k_vk->vkCreateDevice(m_vkPhysicalDevice, &deviceCi, nullptr, &m_vkDevice), VK_SUCCESS); ASSERT_TRUE(m_vkDevice != VK_NULL_HANDLE); } }; const VulkanDispatch* DisplayVkTest::k_vk = nullptr; TEST_F(DisplayVkTest, Init) {} TEST_F(DisplayVkTest, PostWithoutSurfaceShouldntCrash) { uint32_t textureWidth = 20; uint32_t textureHeight = 40; DisplayVk displayVk(*k_vk, m_vkPhysicalDevice, m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex, m_vkDevice, m_compositorVkQueue, m_compositorVkQueueLock, m_swapChainVkQueue, m_swapChainVkQueueLock); auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, m_vkCommandPool, textureWidth, textureHeight); std::vector pixels(textureWidth * textureHeight, 0); ASSERT_TRUE(texture->write(pixels)); const auto imageInfo = createBorrowedImageInfo(texture); displayVk.post(imageInfo.get()); } TEST_F(DisplayVkTest, SimplePost) { uint32_t textureWidth = 20; uint32_t textureHeight = 40; auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, m_vkCommandPool, textureWidth, textureHeight); std::vector pixels(textureWidth * textureHeight); for (int i = 0; i < textureHeight; i++) { for (int j = 0; j < textureWidth; j++) { uint8_t *pixel = reinterpret_cast(&pixels[i * textureWidth + j]); pixel[0] = static_cast((i * 0xff / textureHeight) & 0xff); pixel[1] = static_cast((j * 0xff / textureWidth) & 0xff); pixel[2] = 0; pixel[3] = 0xff; } } ASSERT_TRUE(texture->write(pixels)); std::vector> waitForGpuFutures; for (uint32_t i = 0; i < 10; i++) { const auto imageInfo = createBorrowedImageInfo(texture); auto postResult = m_displayVk->post(imageInfo.get()); ASSERT_TRUE(postResult.success); waitForGpuFutures.emplace_back(std::move(postResult.postCompletedWaitable)); } for (auto &waitForGpuFuture : waitForGpuFutures) { waitForGpuFuture.wait(); } } TEST_F(DisplayVkTest, PostTwoColorBuffers) { uint32_t textureWidth = 20; uint32_t textureHeight = 40; auto redTexture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, m_vkCommandPool, textureWidth, textureHeight); auto greenTexture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue, m_vkCommandPool, textureWidth, textureHeight); uint32_t red = 0xff0000ff; uint32_t green = 0xff00ff00; std::vector redPixels(textureWidth * textureHeight, red); std::vector greenPixels(textureWidth * textureHeight, green); ASSERT_TRUE(redTexture->write(redPixels)); ASSERT_TRUE(greenTexture->write(greenPixels)); std::vector> waitForGpuFutures; for (uint32_t i = 0; i < 10; i++) { const auto redImageInfo = createBorrowedImageInfo(redTexture); const auto greenImageInfo = createBorrowedImageInfo(greenTexture); auto redPostResult = m_displayVk->post(redImageInfo.get()); ASSERT_TRUE(redPostResult.success); waitForGpuFutures.emplace_back(std::move(redPostResult.postCompletedWaitable)); auto greenPostResult = m_displayVk->post(greenImageInfo.get()); ASSERT_TRUE(greenPostResult.success); waitForGpuFutures.emplace_back(std::move(greenPostResult.postCompletedWaitable)); } for (auto &waitForGpuFuture : waitForGpuFutures) { waitForGpuFuture.wait(); } } } // namespace } // namespace vk } // namespace gfxstream