| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <android/log.h> |
| #include <driver.h> |
| #include <gmock/gmock.h> |
| #include <graphicsenv/GraphicsEnv.h> |
| #include <gtest/gtest.h> |
| #include <media/NdkImageReader.h> |
| #include <system/window.h> |
| #include <vulkan/vulkan.h> |
| |
| #define LOGI(...) \ |
| __android_log_print(ANDROID_LOG_INFO, "libvulkan_test", __VA_ARGS__) |
| #define LOGE(...) \ |
| __android_log_print(ANDROID_LOG_ERROR, "libvulkan_test", __VA_ARGS__) |
| |
| #define VK_CHECK(result) ASSERT_EQ(VK_SUCCESS, result) |
| |
| namespace android { |
| |
| namespace libvulkantest { |
| |
| class AImageReaderVulkanSwapchainTest : public ::testing::Test { |
| public: |
| AImageReaderVulkanSwapchainTest() {} |
| |
| AImageReader* mReader = nullptr; |
| ANativeWindow* mWindow = nullptr; |
| VkInstance mVkInstance = VK_NULL_HANDLE; |
| VkPhysicalDevice mPhysicalDev = VK_NULL_HANDLE; |
| VkDevice mDevice = VK_NULL_HANDLE; |
| VkSurfaceKHR mSurface = VK_NULL_HANDLE; |
| VkQueue mPresentQueue = VK_NULL_HANDLE; |
| uint32_t mPresentQueueFamily = UINT32_MAX; |
| VkSwapchainKHR mSwapchain = VK_NULL_HANDLE; |
| |
| void SetUp() override { |
| // We need this to run before any other vulkan tests. Otherwise the |
| // layers are already marked as loaded |
| auto app_namespace = |
| android::GraphicsEnv::getInstance().getAppNamespace(); |
| android::GraphicsEnv::getInstance().setLayerPaths( |
| app_namespace, |
| "/data/local/tmp/libvulkan_test/x86_64/:/data/local/tmp/" |
| "libvulkan_test/arm64/"); |
| } |
| |
| void TearDown() override {} |
| |
| // ------------------------------------------------------ |
| // Helper methods |
| // ------------------------------------------------------ |
| |
| void createVulkanInstance(std::vector<const char*>& layers) { |
| const char* extensions[] = { |
| VK_KHR_SURFACE_EXTENSION_NAME, |
| VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, |
| VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, |
| VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, |
| }; |
| |
| VkApplicationInfo appInfo{}; |
| appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; |
| appInfo.pApplicationName = "AImageReader Vulkan Swapchain Test"; |
| appInfo.applicationVersion = 1; |
| appInfo.pEngineName = "TestEngine"; |
| appInfo.engineVersion = 1; |
| appInfo.apiVersion = VK_API_VERSION_1_0; |
| |
| VkInstanceCreateInfo instInfo{}; |
| instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; |
| instInfo.pApplicationInfo = &appInfo; |
| instInfo.enabledExtensionCount = |
| sizeof(extensions) / sizeof(extensions[0]); |
| instInfo.ppEnabledExtensionNames = extensions; |
| instInfo.enabledLayerCount = layers.size(); |
| instInfo.ppEnabledLayerNames = layers.data(); |
| VkResult res = vkCreateInstance(&instInfo, nullptr, &mVkInstance); |
| VK_CHECK(res); |
| LOGE("Vulkan instance created"); |
| } |
| |
| void createAImageReader(int width, |
| int height, |
| int format, |
| int maxImages, |
| bool set_listener = true) { |
| media_status_t status = |
| AImageReader_new(width, height, format, maxImages, &mReader); |
| ASSERT_EQ(AMEDIA_OK, status) << "Failed to create AImageReader"; |
| ASSERT_NE(nullptr, mReader) << "AImageReader is null"; |
| |
| if (set_listener) { |
| // Optionally set a listener |
| AImageReader_ImageListener listener{}; |
| listener.context = this; |
| listener.onImageAvailable = |
| &AImageReaderVulkanSwapchainTest::onImageAvailable; |
| AImageReader_setImageListener(mReader, &listener); |
| |
| LOGI("AImageReader created with %dx%d, format=%d", width, height, |
| format); |
| } |
| } |
| |
| void getANativeWindowFromReader() { |
| ASSERT_NE(nullptr, mReader); |
| |
| media_status_t status = AImageReader_getWindow(mReader, &mWindow); |
| ASSERT_EQ(AMEDIA_OK, status) |
| << "Failed to get ANativeWindow from AImageReader"; |
| ASSERT_NE(nullptr, mWindow) << "ANativeWindow is null"; |
| LOGI("ANativeWindow obtained from AImageReader"); |
| } |
| |
| void createVulkanSurface() { |
| ASSERT_NE((VkInstance)VK_NULL_HANDLE, mVkInstance); |
| ASSERT_NE((ANativeWindow*)nullptr, mWindow); |
| |
| VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{}; |
| surfaceCreateInfo.sType = |
| VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; |
| surfaceCreateInfo.window = mWindow; |
| |
| VkResult res = vkCreateAndroidSurfaceKHR( |
| mVkInstance, &surfaceCreateInfo, nullptr, &mSurface); |
| VK_CHECK(res); |
| LOGI("Vulkan surface created from ANativeWindow"); |
| } |
| |
| void pickPhysicalDeviceAndQueueFamily() { |
| ASSERT_NE((VkInstance)VK_NULL_HANDLE, mVkInstance); |
| |
| uint32_t deviceCount = 0; |
| vkEnumeratePhysicalDevices(mVkInstance, &deviceCount, nullptr); |
| ASSERT_GT(deviceCount, 0U) << "No Vulkan physical devices found!"; |
| |
| std::vector<VkPhysicalDevice> devices(deviceCount); |
| vkEnumeratePhysicalDevices(mVkInstance, &deviceCount, devices.data()); |
| |
| for (auto& dev : devices) { |
| uint32_t queueFamilyCount = 0; |
| vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount, |
| nullptr); |
| std::vector<VkQueueFamilyProperties> queueProps(queueFamilyCount); |
| vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount, |
| queueProps.data()); |
| |
| for (uint32_t i = 0; i < queueFamilyCount; i++) { |
| VkBool32 support = VK_FALSE; |
| vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, mSurface, |
| &support); |
| if (support == VK_TRUE) { |
| // Found a queue family that can present |
| mPhysicalDev = dev; |
| mPresentQueueFamily = i; |
| |
| LOGI( |
| "Physical device found with queue family %u supporting " |
| "present", |
| i); |
| return; |
| } |
| } |
| } |
| |
| FAIL() |
| << "No physical device found that supports present to the surface!"; |
| } |
| |
| void createDeviceAndGetQueue(std::vector<const char*>& layers, |
| std::vector<const char*> inExtensions = {}, |
| void* pNext = nullptr) { |
| ASSERT_NE((void*)VK_NULL_HANDLE, mPhysicalDev); |
| ASSERT_NE(UINT32_MAX, mPresentQueueFamily); |
| |
| float queuePriority = 1.0f; |
| VkDeviceQueueCreateInfo queueInfo{}; |
| queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; |
| queueInfo.queueFamilyIndex = mPresentQueueFamily; |
| queueInfo.queueCount = 1; |
| queueInfo.pQueuePriorities = &queuePriority; |
| |
| VkDeviceCreateInfo deviceInfo{}; |
| deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; |
| deviceInfo.pNext = pNext; |
| deviceInfo.queueCreateInfoCount = 1; |
| deviceInfo.pQueueCreateInfos = &queueInfo; |
| deviceInfo.enabledLayerCount = layers.size(); |
| deviceInfo.ppEnabledLayerNames = layers.data(); |
| |
| std::vector<const char*> extensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| }; |
| for (auto extension : inExtensions) { |
| extensions.push_back(extension); |
| } |
| deviceInfo.enabledExtensionCount = extensions.size(); |
| deviceInfo.ppEnabledExtensionNames = extensions.data(); |
| |
| VkResult res = |
| vkCreateDevice(mPhysicalDev, &deviceInfo, nullptr, &mDevice); |
| VK_CHECK(res); |
| LOGI("Logical device created"); |
| |
| vkGetDeviceQueue(mDevice, mPresentQueueFamily, 0, &mPresentQueue); |
| ASSERT_NE((VkQueue)VK_NULL_HANDLE, mPresentQueue); |
| LOGI("Acquired present-capable queue"); |
| } |
| |
| void createSwapchain() { |
| ASSERT_NE((VkDevice)VK_NULL_HANDLE, mDevice); |
| ASSERT_NE((VkSurfaceKHR)VK_NULL_HANDLE, mSurface); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( |
| mPhysicalDev, mSurface, &surfaceCaps)); |
| |
| uint32_t formatCount = 0; |
| vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface, |
| &formatCount, nullptr); |
| ASSERT_GT(formatCount, 0U); |
| std::vector<VkSurfaceFormatKHR> formats(formatCount); |
| vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface, |
| &formatCount, formats.data()); |
| |
| VkSurfaceFormatKHR chosenFormat = formats[0]; |
| LOGI("Chosen surface format: %d", chosenFormat.format); |
| |
| uint32_t presentModeCount = 0; |
| vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDev, mSurface, |
| &presentModeCount, nullptr); |
| ASSERT_GT(presentModeCount, 0U); |
| std::vector<VkPresentModeKHR> presentModes(presentModeCount); |
| vkGetPhysicalDeviceSurfacePresentModesKHR( |
| mPhysicalDev, mSurface, &presentModeCount, presentModes.data()); |
| |
| VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR; |
| for (auto mode : presentModes) { |
| if (mode == VK_PRESENT_MODE_FIFO_KHR) { |
| chosenPresentMode = mode; |
| break; |
| } |
| } |
| LOGI("Chosen present mode: %d", chosenPresentMode); |
| |
| VkExtent2D swapchainExtent{}; |
| if (surfaceCaps.currentExtent.width == 0xFFFFFFFF) { |
| swapchainExtent.width = 640; // fallback |
| swapchainExtent.height = 480; // fallback |
| } else { |
| swapchainExtent = surfaceCaps.currentExtent; |
| } |
| LOGI("Swapchain extent: %d x %d", swapchainExtent.width, |
| swapchainExtent.height); |
| |
| uint32_t desiredImageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| desiredImageCount > surfaceCaps.maxImageCount) { |
| desiredImageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = desiredImageCount; |
| swapchainInfo.imageFormat = chosenFormat.format; |
| swapchainInfo.imageColorSpace = chosenFormat.colorSpace; |
| swapchainInfo.imageExtent = swapchainExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = chosenPresentMode; |
| swapchainInfo.clipped = VK_TRUE; |
| swapchainInfo.oldSwapchain = VK_NULL_HANDLE; |
| |
| uint32_t queueFamilyIndices[] = {mPresentQueueFamily}; |
| swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| swapchainInfo.queueFamilyIndexCount = 1; |
| swapchainInfo.pQueueFamilyIndices = queueFamilyIndices; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| if (res == VK_SUCCESS) { |
| LOGI("Swapchain created successfully"); |
| |
| uint32_t swapchainImageCount = 0; |
| vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, |
| nullptr); |
| std::vector<VkImage> swapchainImages(swapchainImageCount); |
| vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, |
| swapchainImages.data()); |
| |
| LOGI("Swapchain has %u images", swapchainImageCount); |
| } else { |
| LOGI("Swapchain creation failed"); |
| } |
| } |
| |
| // Image available callback (AImageReader) |
| static void onImageAvailable(void*, AImageReader* reader) { |
| LOGI("onImageAvailable callback triggered"); |
| AImage* image = nullptr; |
| media_status_t status = AImageReader_acquireLatestImage(reader, &image); |
| if (status != AMEDIA_OK || !image) { |
| LOGE("Failed to acquire latest image"); |
| return; |
| } |
| AImage_delete(image); |
| LOGI("Released acquired image"); |
| } |
| |
| void cleanUpSwapchainForTest() { |
| if (mSwapchain != VK_NULL_HANDLE) { |
| vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr); |
| mSwapchain = VK_NULL_HANDLE; |
| } |
| if (mDevice != VK_NULL_HANDLE) { |
| vkDestroyDevice(mDevice, nullptr); |
| mDevice = VK_NULL_HANDLE; |
| } |
| if (mSurface != VK_NULL_HANDLE) { |
| vkDestroySurfaceKHR(mVkInstance, mSurface, nullptr); |
| mSurface = VK_NULL_HANDLE; |
| } |
| if (mVkInstance != VK_NULL_HANDLE) { |
| vkDestroyInstance(mVkInstance, nullptr); |
| mVkInstance = VK_NULL_HANDLE; |
| } |
| if (mReader) { |
| // AImageReader_delete(mReader); |
| mReader = nullptr; |
| } |
| // Note: The ANativeWindow from AImageReader is implicitly |
| // managed by the reader, so we don't explicitly delete it. |
| mWindow = nullptr; |
| } |
| |
| void buildSwapchianForTest(std::vector<const char*>& instanceLayers, |
| std::vector<const char*>& deviceLayers) { |
| createVulkanInstance(instanceLayers); |
| |
| // the "atest libvulkan_test" command will execute this test as a binary |
| // (not apk) on the device. Consequently we can't render to the screen |
| // and need to work around this by using AImageReader* |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| createDeviceAndGetQueue(deviceLayers); |
| createSwapchain(); |
| } |
| }; |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, TestHelperMethods) { |
| // Verify that the basic plumbing/helper functions of these tests is |
| // working. This doesn't directly test any of the layer code. It only |
| // verifies that we can successfully create a swapchain with an AImageReader |
| |
| std::vector<const char*> instanceLayers; |
| std::vector<const char*> deviceLayers; |
| buildSwapchianForTest(deviceLayers, instanceLayers); |
| |
| ASSERT_NE(mVkInstance, (VkInstance)VK_NULL_HANDLE); |
| ASSERT_NE(mPhysicalDev, (VkPhysicalDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mDevice, (VkDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mSurface, (VkSurfaceKHR)VK_NULL_HANDLE); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| cleanUpSwapchainForTest(); |
| } |
| |
| // Passing state in these tests requires global state. Wrap each test in an |
| // anonymous namespace to prevent conflicting names. |
| namespace { |
| |
| VKAPI_ATTR VkResult VKAPI_CALL hookedGetPhysicalDeviceImageFormatProperties2KHR( |
| VkPhysicalDevice, |
| const VkPhysicalDeviceImageFormatInfo2*, |
| VkImageFormatProperties2*) { |
| return VK_ERROR_SURFACE_LOST_KHR; |
| } |
| |
| static PFN_vkGetSwapchainGrallocUsage2ANDROID |
| pfnNextGetSwapchainGrallocUsage2ANDROID = nullptr; |
| |
| static bool g_grallocCalled = false; |
| |
| VKAPI_ATTR VkResult VKAPI_CALL hookGetSwapchainGrallocUsage2ANDROID( |
| VkDevice device, |
| VkFormat format, |
| VkImageUsageFlags imageUsage, |
| VkSwapchainImageUsageFlagsANDROID swapchainImageUsage, |
| uint64_t* grallocConsumerUsage, |
| uint64_t* grallocProducerUsage) { |
| g_grallocCalled = true; |
| if (pfnNextGetSwapchainGrallocUsage2ANDROID) { |
| return pfnNextGetSwapchainGrallocUsage2ANDROID( |
| device, format, imageUsage, swapchainImageUsage, |
| grallocConsumerUsage, grallocProducerUsage); |
| } |
| |
| return VK_ERROR_INITIALIZATION_FAILED; |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, getProducerUsageFallbackTest1) { |
| // BUG: 379230826 |
| // Verify that getProducerUsage falls back to |
| // GetSwapchainGrallocUsage*ANDROID if GPDIFP2 fails |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| createVulkanInstance(instanceLayers); |
| |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| createDeviceAndGetQueue(deviceLayers); |
| auto& pdev = vulkan::driver::GetData(mDevice).driver_physical_device; |
| auto& pdevDispatchTable = vulkan::driver::GetData(pdev).driver; |
| auto& deviceDispatchTable = vulkan::driver::GetData(mDevice).driver; |
| |
| ASSERT_NE(deviceDispatchTable.GetSwapchainGrallocUsage2ANDROID, nullptr); |
| |
| pdevDispatchTable.GetPhysicalDeviceImageFormatProperties2 = |
| hookedGetPhysicalDeviceImageFormatProperties2KHR; |
| deviceDispatchTable.GetSwapchainGrallocUsage2ANDROID = |
| hookGetSwapchainGrallocUsage2ANDROID; |
| |
| ASSERT_FALSE(g_grallocCalled); |
| |
| createSwapchain(); |
| |
| ASSERT_TRUE(g_grallocCalled); |
| |
| ASSERT_NE(mVkInstance, (VkInstance)VK_NULL_HANDLE); |
| ASSERT_NE(mPhysicalDev, (VkPhysicalDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mDevice, (VkDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mSurface, (VkSurfaceKHR)VK_NULL_HANDLE); |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace |
| |
| // Passing state in these tests requires global state. Wrap each test in an |
| // anonymous namespace to prevent conflicting names. |
| namespace { |
| |
| static bool g_returnNotSupportedOnce = true; |
| |
| VKAPI_ATTR VkResult VKAPI_CALL |
| Hook_GetPhysicalDeviceImageFormatProperties2_NotSupportedOnce( |
| VkPhysicalDevice /*physicalDevice*/, |
| const VkPhysicalDeviceImageFormatInfo2* /*pImageFormatInfo*/, |
| VkImageFormatProperties2* /*pImageFormatProperties*/) { |
| if (g_returnNotSupportedOnce) { |
| g_returnNotSupportedOnce = false; |
| return VK_ERROR_FORMAT_NOT_SUPPORTED; |
| } |
| return VK_SUCCESS; |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, SurfaceFormats2KHR_IgnoreNotSupported) { |
| // BUG: 357903074 |
| // Verify that vkGetPhysicalDeviceSurfaceFormats2KHR properly |
| // ignores VK_ERROR_FORMAT_NOT_SUPPORTED and continues enumerating formats. |
| std::vector<const char*> instanceLayers; |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| auto& pdevDispatchTable = vulkan::driver::GetData(mPhysicalDev).driver; |
| pdevDispatchTable.GetPhysicalDeviceImageFormatProperties2 = |
| Hook_GetPhysicalDeviceImageFormatProperties2_NotSupportedOnce; |
| |
| PFN_vkGetPhysicalDeviceSurfaceFormats2KHR |
| pfnGetPhysicalDeviceSurfaceFormats2KHR = |
| reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormats2KHR>( |
| vkGetInstanceProcAddr(mVkInstance, |
| "vkGetPhysicalDeviceSurfaceFormats2KHR")); |
| ASSERT_NE(nullptr, pfnGetPhysicalDeviceSurfaceFormats2KHR) |
| << "Could not get pointer to vkGetPhysicalDeviceSurfaceFormats2KHR"; |
| |
| VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo2{}; |
| surfaceInfo2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR; |
| surfaceInfo2.pNext = nullptr; |
| surfaceInfo2.surface = mSurface; |
| |
| uint32_t formatCount = 0; |
| VkResult res = pfnGetPhysicalDeviceSurfaceFormats2KHR( |
| mPhysicalDev, &surfaceInfo2, &formatCount, nullptr); |
| |
| // If the loader never tries a second format, it might fail or 0-out the |
| // formatCount. The patch ensures it continues to the next format rather |
| // than bailing out on the first NOT_SUPPORTED. |
| ASSERT_EQ(VK_SUCCESS, res) |
| << "vkGetPhysicalDeviceSurfaceFormats2KHR failed unexpectedly"; |
| ASSERT_GT(formatCount, 0U) |
| << "No surface formats found; the loader may have bailed early."; |
| |
| std::vector<VkSurfaceFormat2KHR> formats(formatCount); |
| for (auto& f : formats) { |
| f.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR; |
| f.pNext = nullptr; |
| } |
| res = pfnGetPhysicalDeviceSurfaceFormats2KHR(mPhysicalDev, &surfaceInfo2, |
| &formatCount, formats.data()); |
| ASSERT_EQ(VK_SUCCESS, res) << "Failed to retrieve surface formats"; |
| |
| LOGI( |
| "SurfaceFormats2KHR_IgnoreNotSupported test: found %u formats after " |
| "ignoring NOT_SUPPORTED", |
| formatCount); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace |
| |
| namespace { |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, MutableFormatSwapchainTest) { |
| // Test swapchain with mutable format extension |
| std::vector<const char*> instanceLayers; |
| std::vector<const char*> deviceLayers; |
| std::vector<const char*> deviceExtensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME, |
| VK_KHR_MAINTENANCE2_EXTENSION_NAME, |
| VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME}; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions); |
| |
| ASSERT_NE((VkDevice)VK_NULL_HANDLE, mDevice); |
| ASSERT_NE((VkSurfaceKHR)VK_NULL_HANDLE, mSurface); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t formatCount = 0; |
| vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface, &formatCount, |
| nullptr); |
| ASSERT_GT(formatCount, 0U); |
| std::vector<VkSurfaceFormatKHR> formats(formatCount); |
| vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface, &formatCount, |
| formats.data()); |
| |
| VkFormat viewFormats[2] = {formats[0].format, formats[0].format}; |
| if (formatCount > 1) { |
| viewFormats[1] = formats[1].format; |
| } |
| |
| VkImageFormatListCreateInfoKHR formatList{}; |
| formatList.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR; |
| formatList.viewFormatCount = 2; |
| formatList.pViewFormats = viewFormats; |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.pNext = &formatList; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = surfaceCaps.minImageCount + 1; |
| swapchainInfo.imageFormat = formats[0].format; |
| swapchainInfo.imageColorSpace = formats[0].colorSpace; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| swapchainInfo.flags = VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; |
| |
| uint32_t queueFamilyIndices[] = {mPresentQueueFamily}; |
| swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| swapchainInfo.queueFamilyIndexCount = 1; |
| swapchainInfo.pQueueFamilyIndices = queueFamilyIndices; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| if (res == VK_SUCCESS) { |
| LOGI("Mutable format swapchain created successfully"); |
| |
| uint32_t imageCount = 0; |
| vkGetSwapchainImagesKHR(mDevice, mSwapchain, &imageCount, nullptr); |
| ASSERT_GT(imageCount, 0U); |
| } else { |
| LOGI( |
| "Mutable format swapchain creation failed (extension may not be " |
| "supported)"); |
| } |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace |
| |
| int Hook_ANativeWindow_Perform_FakeTimestamps(ANativeWindow* window, |
| ANativeWindow_performFn perform, |
| void*, |
| int operation, |
| va_list args) { |
| if (operation == NATIVE_WINDOW_GET_FRAME_TIMESTAMPS) { |
| ALOGI("Intercepted NATIVE_WINDOW_GET_FRAME_TIMESTAMPS"); |
| uint64_t frameId = va_arg(args, uint64_t); |
| int64_t* outRequestedPresentTime = va_arg(args, int64_t*); |
| int64_t* outAcquireTime = va_arg(args, int64_t*); |
| int64_t* outLatchTime = va_arg(args, int64_t*); |
| int64_t* outFirstRefreshStartTime = va_arg(args, int64_t*); |
| int64_t* outLastRefreshStartTime = va_arg(args, int64_t*); |
| int64_t* outGpuCompositionDoneTime = va_arg(args, int64_t*); |
| int64_t* outDisplayPresentTime = va_arg(args, int64_t*); |
| int64_t* outDequeueReadyTime = va_arg(args, int64_t*); |
| int64_t* outReleaseTime = va_arg(args, int64_t*); |
| |
| if (outRequestedPresentTime) |
| *outRequestedPresentTime = frameId; |
| if (outAcquireTime) |
| *outAcquireTime = frameId; |
| if (outLatchTime) |
| *outLatchTime = frameId; |
| if (outFirstRefreshStartTime) |
| *outFirstRefreshStartTime = frameId; |
| if (outLastRefreshStartTime) |
| *outLastRefreshStartTime = frameId; |
| if (outGpuCompositionDoneTime) |
| *outGpuCompositionDoneTime = frameId; |
| if (outDisplayPresentTime) |
| *outDisplayPresentTime = frameId; |
| if (outDequeueReadyTime) |
| *outDequeueReadyTime = frameId; |
| if (outReleaseTime) |
| *outReleaseTime = frameId; |
| |
| return 0; |
| } |
| |
| return perform(window, operation, args); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, FrameTimestampInterceptorTest) { |
| // verify native_window_get_frame_timestamp can be intercepted |
| std::vector<const char*> instanceLayers; |
| std::vector<const char*> deviceLayers; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| ASSERT_NE(mWindow, nullptr); |
| |
| int result = mWindow->perform( |
| mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_FakeTimestamps, nullptr /* data */); |
| ASSERT_EQ(result, 0); |
| |
| int64_t latchTime = 0; |
| native_window_get_frame_timestamps(mWindow, 123, nullptr, nullptr, |
| &latchTime, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr); |
| |
| ASSERT_EQ(latchTime, 123); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, StandardPresentTimingCountTest) { |
| // verify we only wait 1 frame for timing results b/440824659 |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| |
| int result = mWindow->perform( |
| mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_FakeTimestamps, nullptr /* data */); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| // Check if the required display timing extension is supported by the |
| // device. |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool timingExtSupported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME) == 0) { |
| timingExtSupported = true; |
| break; |
| } |
| } |
| |
| if (!timingExtSupported) { |
| GTEST_SKIP() << "Vulkan extension " |
| << VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME |
| << " not supported."; |
| } |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| VkPresentTimeGOOGLE presentTime = {1, 1000}; |
| VkPresentTimesInfoGOOGLE presentTimesInfo = {}; |
| presentTimesInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE; |
| presentTimesInfo.swapchainCount = 1; |
| presentTimesInfo.pTimes = &presentTime; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentTimesInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &mSwapchain; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| auto pfnGetPastPresentationTimingGOOGLE = |
| (PFN_vkGetPastPresentationTimingGOOGLE)vkGetDeviceProcAddr( |
| mDevice, "vkGetPastPresentationTimingGOOGLE"); |
| ASSERT_NE(pfnGetPastPresentationTimingGOOGLE, |
| (PFN_vkGetPastPresentationTimingGOOGLE) nullptr); |
| |
| uint32_t timingCount = 0; |
| res = pfnGetPastPresentationTimingGOOGLE(mDevice, mSwapchain, &timingCount, |
| nullptr); |
| VK_CHECK(res); |
| |
| ASSERT_TRUE(timingCount == 1) |
| << "Expected one timing record to be available."; |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, SharedPresentTimingZeroedTest) { |
| // Verifies that for shared present modes, vkGetPastPresentationTimingGOOGLE |
| // returns a record with zeroed-out timing values, as per the change in |
| // get_num_ready_timings. |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool timingExtSupported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME) == 0) { |
| timingExtSupported = true; |
| break; |
| } |
| } |
| |
| if (!timingExtSupported) { |
| GTEST_SKIP() << "Vulkan extension " |
| << VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME |
| << " not supported."; |
| } |
| |
| uint32_t presentModeCount = 0; |
| vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDev, mSurface, |
| &presentModeCount, nullptr); |
| ASSERT_GT(presentModeCount, 0U); |
| std::vector<VkPresentModeKHR> presentModes(presentModeCount); |
| vkGetPhysicalDeviceSurfacePresentModesKHR( |
| mPhysicalDev, mSurface, &presentModeCount, presentModes.data()); |
| |
| bool sharedPresentSupported = false; |
| for (const auto& mode : presentModes) { |
| if (mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR) { |
| sharedPresentSupported = true; |
| break; |
| } |
| } |
| |
| if (!sharedPresentSupported) { |
| GTEST_SKIP() |
| << "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR not supported."; |
| } |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = 1; |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| ASSERT_EQ(imageIndex, 0U); |
| |
| VkPresentTimeGOOGLE presentTime = {123, 456}; |
| VkPresentTimesInfoGOOGLE presentTimesInfo = {}; |
| presentTimesInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE; |
| presentTimesInfo.swapchainCount = 1; |
| presentTimesInfo.pTimes = &presentTime; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentTimesInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &mSwapchain; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| |
| auto pfnGetPastPresentationTimingGOOGLE = |
| (PFN_vkGetPastPresentationTimingGOOGLE)vkGetDeviceProcAddr( |
| mDevice, "vkGetPastPresentationTimingGOOGLE"); |
| ASSERT_NE(pfnGetPastPresentationTimingGOOGLE, |
| (PFN_vkGetPastPresentationTimingGOOGLE) nullptr); |
| |
| uint32_t timingCount = 0; |
| res = pfnGetPastPresentationTimingGOOGLE(mDevice, mSwapchain, &timingCount, |
| nullptr); |
| VK_CHECK(res); |
| ASSERT_EQ(timingCount, 1U); |
| |
| VkPastPresentationTimingGOOGLE timing; |
| timingCount = 1; |
| res = pfnGetPastPresentationTimingGOOGLE(mDevice, mSwapchain, &timingCount, |
| &timing); |
| VK_CHECK(res); |
| ASSERT_EQ(timingCount, 1U); |
| |
| // Verify the new behavior: present ID is preserved, but times are zero. |
| EXPECT_EQ(timing.presentID, presentTime.presentID); |
| EXPECT_EQ(timing.actualPresentTime, 0U); |
| EXPECT_EQ(timing.earliestPresentTime, 0U); |
| EXPECT_EQ(timing.presentMargin, 0U); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, LoadInstanceLayerAndDeviceLayer) { |
| // Verify our layer works as both a device and instance layer |
| // building the swapchain will fail if the layer is not loaded |
| |
| std::vector<const char*> instanceLayers = { |
| "VK_LAYER_libVulkanTestLayer", |
| }; |
| std::vector<const char*> deviceLayers = { |
| "VK_LAYER_libVulkanTestLayer", |
| }; |
| buildSwapchianForTest(deviceLayers, instanceLayers); |
| ASSERT_NE(mVkInstance, (VkInstance)VK_NULL_HANDLE); |
| ASSERT_NE(mPhysicalDev, (VkPhysicalDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mDevice, (VkDevice)VK_NULL_HANDLE); |
| ASSERT_NE(mSurface, (VkSurfaceKHR)VK_NULL_HANDLE); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, |
| FailToLoadInstanceLayerAndDeviceLayerWithBadName) { |
| // Verify .so files without "libVkLayer_" prefix are not loaded |
| // building the swapchain will fail if the layer is not loaded |
| |
| std::vector<const char*> instanceLayers = { |
| "VK_LAYER_libVulkanTestLayer_without_libVkLayer_prefix", |
| }; |
| |
| const char* extensions[] = { |
| VK_KHR_SURFACE_EXTENSION_NAME, |
| VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, |
| }; |
| |
| VkApplicationInfo appInfo{}; |
| appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; |
| appInfo.pApplicationName = "AImageReader Vulkan Swapchain Test"; |
| appInfo.applicationVersion = 1; |
| appInfo.pEngineName = "TestEngine"; |
| appInfo.engineVersion = 1; |
| appInfo.apiVersion = VK_API_VERSION_1_0; |
| |
| VkInstanceCreateInfo instInfo{}; |
| instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; |
| instInfo.pApplicationInfo = &appInfo; |
| instInfo.enabledExtensionCount = sizeof(extensions) / sizeof(extensions[0]); |
| instInfo.ppEnabledExtensionNames = extensions; |
| instInfo.enabledLayerCount = instanceLayers.size(); |
| instInfo.ppEnabledLayerNames = instanceLayers.data(); |
| VkResult res = vkCreateInstance(&instInfo, nullptr, &mVkInstance); |
| |
| EXPECT_EQ(res, VK_ERROR_LAYER_NOT_PRESENT); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, PresentWait2Test) { |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 1, false); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool presentId2Supported = false; |
| bool presentWait2Supported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME) == 0) { |
| presentId2Supported = true; |
| } |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME) == 0) { |
| presentWait2Supported = true; |
| } |
| } |
| |
| if (!presentId2Supported || !presentWait2Supported) { |
| GTEST_SKIP() |
| << "VK_KHR_present_id2 or VK_KHR_present_wait2 not supported"; |
| } |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| swapchainInfo.flags = VK_SWAPCHAIN_CREATE_PRESENT_WAIT_2_BIT_KHR; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| auto pfnWaitForPresent2KHR = (PFN_vkWaitForPresent2KHR)vkGetDeviceProcAddr( |
| mDevice, "vkWaitForPresent2KHR"); |
| ASSERT_NE(pfnWaitForPresent2KHR, (PFN_vkWaitForPresent2KHR) nullptr); |
| |
| uint64_t presentId = 1; |
| |
| VkPresentWait2InfoKHR presentWaitInfo{}; |
| presentWaitInfo.sType = VK_STRUCTURE_TYPE_PRESENT_WAIT_2_INFO_KHR; |
| presentWaitInfo.presentId = 1; |
| presentWaitInfo.timeout = 10000; |
| |
| // Wait for a present that hasn't been queued. Counterintuitively this |
| // should pass as we assume any unseen frames are past prevented frames |
| res = pfnWaitForPresent2KHR(mDevice, mSwapchain, &presentWaitInfo); |
| ASSERT_EQ(res, VK_SUCCESS); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| VkPresentId2KHR presentIdInfo = {}; |
| presentIdInfo.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo.swapchainCount = 1; |
| presentIdInfo.pPresentIds = &presentId; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentIdInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &mSwapchain; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| |
| // Make sure we get a timeout for a frame which remains in flight |
| res = pfnWaitForPresent2KHR(mDevice, mSwapchain, &presentWaitInfo); |
| ASSERT_EQ(res, VK_TIMEOUT); |
| |
| // Call this to trigger the frame acquired callback in |
| // BufferQueueConsumer::acquireBufer |
| AImage* image = nullptr; |
| media_status_t status = AImageReader_acquireLatestImage(mReader, &image); |
| |
| // Wait for the presentId we just submitted. Should succeed. |
| res = pfnWaitForPresent2KHR(mDevice, mSwapchain, &presentWaitInfo); |
| ASSERT_EQ(res, VK_SUCCESS); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, PresentWait2ManyFrames) { |
| // Test vk_khr_present_wait2 with many frames. This also tests the |
| // native_window_get_last_replaced_frame_id logic inside swapchain.cpp |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 1, false); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool presentId2Supported = false; |
| bool presentWait2Supported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME) == 0) { |
| presentId2Supported = true; |
| } |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME) == 0) { |
| presentWait2Supported = true; |
| } |
| } |
| |
| if (!presentId2Supported || !presentWait2Supported) { |
| GTEST_SKIP() |
| << "VK_KHR_present_id2 or VK_KHR_present_wait2 not supported"; |
| } |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions); |
| |
| uint32_t presentModeCount = 0; |
| vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDev, mSurface, |
| &presentModeCount, nullptr); |
| ASSERT_GT(presentModeCount, 0U); |
| std::vector<VkPresentModeKHR> presentModes(presentModeCount); |
| vkGetPhysicalDeviceSurfacePresentModesKHR( |
| mPhysicalDev, mSurface, &presentModeCount, presentModes.data()); |
| |
| bool mailboxSupported = false; |
| for (const auto& mode : presentModes) { |
| if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { |
| mailboxSupported = true; |
| break; |
| } |
| } |
| |
| if (!mailboxSupported) { |
| GTEST_SKIP() << "VK_PRESENT_MODE_MAILBOX_KHR not supported."; |
| } |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| if (surfaceCaps.maxImageCount != 0 && surfaceCaps.maxImageCount < 2) { |
| GTEST_SKIP() << "Swapchain doesn't support at least 2 images."; |
| } |
| uint32_t imageCount = std::max(2u, surfaceCaps.minImageCount); |
| if (surfaceCaps.maxImageCount != 0) { |
| imageCount = std::min(imageCount, surfaceCaps.maxImageCount); |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| swapchainInfo.flags = VK_SWAPCHAIN_CREATE_PRESENT_WAIT_2_BIT_KHR; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| auto pfnWaitForPresent2KHR = (PFN_vkWaitForPresent2KHR)vkGetDeviceProcAddr( |
| mDevice, "vkWaitForPresent2KHR"); |
| ASSERT_NE(pfnWaitForPresent2KHR, (PFN_vkWaitForPresent2KHR) nullptr); |
| |
| uint32_t imageIndex1; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex1); |
| VK_CHECK(res); |
| |
| uint64_t presentId1 = 1; |
| VkPresentId2KHR presentIdInfo1 = {}; |
| presentIdInfo1.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo1.swapchainCount = 1; |
| presentIdInfo1.pPresentIds = &presentId1; |
| |
| VkPresentInfoKHR presentInfo1 = {}; |
| presentInfo1.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo1.pNext = &presentIdInfo1; |
| presentInfo1.swapchainCount = 1; |
| presentInfo1.pSwapchains = &mSwapchain; |
| presentInfo1.pImageIndices = &imageIndex1; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo1); |
| VK_CHECK(res); |
| |
| // Present with ID 2, this should replace/drop frame 1 |
| uint32_t imageIndex2; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex2); |
| VK_CHECK(res); |
| |
| uint64_t presentId2 = 2; |
| VkPresentId2KHR presentIdInfo2 = {}; |
| presentIdInfo2.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo2.swapchainCount = 1; |
| presentIdInfo2.pPresentIds = &presentId2; |
| |
| VkPresentInfoKHR presentInfo2 = {}; |
| presentInfo2.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo2.pNext = &presentIdInfo2; |
| presentInfo2.swapchainCount = 1; |
| presentInfo2.pSwapchains = &mSwapchain; |
| presentInfo2.pImageIndices = &imageIndex2; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo2); |
| VK_CHECK(res); |
| |
| // Present with ID 3, this should replace/drop frame 2 |
| uint32_t imageIndex3; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex3); |
| VK_CHECK(res); |
| |
| uint64_t presentId3 = 3; |
| VkPresentId2KHR presentIdInfo3 = {}; |
| presentIdInfo3.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo3.swapchainCount = 1; |
| presentIdInfo3.pPresentIds = &presentId3; |
| |
| VkPresentInfoKHR presentInfo3 = {}; |
| presentInfo3.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo3.pNext = &presentIdInfo3; |
| presentInfo3.swapchainCount = 1; |
| presentInfo3.pSwapchains = &mSwapchain; |
| presentInfo3.pImageIndices = &imageIndex3; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo3); |
| VK_CHECK(res); |
| |
| // Wait for present ID 2, which was dropped by present 3. |
| VkPresentWait2InfoKHR presentWaitInfo2{}; |
| presentWaitInfo2.sType = VK_STRUCTURE_TYPE_PRESENT_WAIT_2_INFO_KHR; |
| presentWaitInfo2.presentId = 2; |
| presentWaitInfo2.timeout = 100000; |
| |
| res = pfnWaitForPresent2KHR(mDevice, mSwapchain, &presentWaitInfo2); |
| ASSERT_EQ(res, VK_SUCCESS); |
| |
| // Wait for present ID 1, which was dropped by present 2 |
| VkPresentWait2InfoKHR presentWaitInfo1{}; |
| presentWaitInfo1.sType = VK_STRUCTURE_TYPE_PRESENT_WAIT_2_INFO_KHR; |
| presentWaitInfo1.presentId = 1; |
| presentWaitInfo1.timeout = 0; |
| |
| res = pfnWaitForPresent2KHR(mDevice, mSwapchain, &presentWaitInfo1); |
| ASSERT_EQ(res, VK_SUCCESS); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, TestFifoLatestReadySupport) { |
| // Test if VK_PRESENT_MODE_FIFO_LATEST_READY_EXT is supported |
| std::vector<const char*> instanceLayers; |
| std::vector<const char*> deviceLayers; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| // Check if the physical device supports the extension |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> extensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| extensions.data()); |
| |
| bool extensionSupported = false; |
| for (const auto& extension : extensions) { |
| if (strcmp(extension.extensionName, |
| "VK_EXT_present_mode_fifo_latest_ready") == 0) { |
| extensionSupported = true; |
| break; |
| } |
| } |
| |
| LOGI("VK_EXT_present_mode_fifo_latest_ready extension supported: %s", |
| extensionSupported ? "YES" : "NO"); |
| |
| createDeviceAndGetQueue(deviceLayers); |
| |
| // Check if the present mode is supported |
| uint32_t presentModeCount = 0; |
| vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDev, mSurface, |
| &presentModeCount, nullptr); |
| std::vector<VkPresentModeKHR> presentModes(presentModeCount); |
| vkGetPhysicalDeviceSurfacePresentModesKHR( |
| mPhysicalDev, mSurface, &presentModeCount, presentModes.data()); |
| |
| bool modeSupported = false; |
| for (auto mode : presentModes) { |
| if (mode == VK_PRESENT_MODE_FIFO_LATEST_READY_EXT) { |
| modeSupported = true; |
| break; |
| } |
| } |
| |
| LOGI( |
| "VK_PRESENT_MODE_FIFO_LATEST_READY_EXT present mode supported " |
| "natively: %s", |
| modeSupported ? "YES" : "NO"); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| static bool HasExtension(std::vector<VkExtensionProperties> const & extensions, |
| char const *extensionName) |
| { |
| for (auto const &ext : extensions) |
| if (!strcmp(ext.extensionName, extensionName)) |
| return true; |
| return false; |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, TestKhrFeaturesCorrectlyExposed) { |
| std::vector<char const *> instanceExtensions10 = { |
| VK_KHR_SURFACE_EXTENSION_NAME, |
| VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, |
| VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, |
| VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, |
| }; |
| |
| VkInstance instance10; |
| |
| VkApplicationInfo appInfo10 = { |
| VK_STRUCTURE_TYPE_APPLICATION_INFO, |
| nullptr, |
| nullptr, |
| 0, |
| nullptr, |
| 0, |
| VK_API_VERSION_1_0, |
| }; |
| |
| VkInstanceCreateInfo ici10 = { |
| VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
| nullptr, |
| 0, |
| &appInfo10, |
| 0, |
| nullptr, |
| static_cast<uint32_t>(instanceExtensions10.size()), |
| instanceExtensions10.data() |
| }; |
| vkCreateInstance(&ici10, nullptr, &instance10); |
| |
| ASSERT_NE(instance10, nullptr); |
| |
| uint32_t deviceCount = 0; |
| vkEnumeratePhysicalDevices(instance10, &deviceCount, nullptr); |
| if (!deviceCount) |
| GTEST_SKIP() << "No vulkan devices"; |
| |
| VkPhysicalDevice physicalDevice; |
| deviceCount = 1; // only fetch first physical device. |
| vkEnumeratePhysicalDevices(instance10, &deviceCount, &physicalDevice); |
| |
| ASSERT_NE(physicalDevice, nullptr); |
| |
| uint32_t deviceExtensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, nullptr); |
| std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount); |
| vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, deviceExtensions.data()); |
| |
| if (!HasExtension(deviceExtensions, VK_KHR_PRESENT_ID_EXTENSION_NAME)) |
| GTEST_SKIP() << "Device does not offer KHR_present_id"; |
| |
| // Now deliberately use the _KHR_ version of the GPDF2 function to fetch the |
| // features associated with this extension. |
| VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures = { |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, |
| nullptr, |
| VK_FALSE, /* present id */ |
| }; |
| VkPhysicalDeviceFeatures2KHR features2 = { |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, |
| &presentIdFeatures, |
| |
| }; |
| |
| auto gpdf2 = (PFN_vkGetPhysicalDeviceFeatures2KHR)vkGetInstanceProcAddr( |
| instance10, "vkGetPhysicalDeviceFeatures2KHR"); |
| gpdf2(physicalDevice, &features2); |
| |
| // Our feature should have been populated! |
| ASSERT_EQ(presentIdFeatures.presentId, VK_TRUE); |
| } |
| |
| namespace { |
| |
| struct OnAcquiredCallbackData { |
| ANativeWindow_OnAcquiredCallback callback = nullptr; |
| void* data = nullptr; |
| }; |
| |
| int Hook_ANativeWindow_Perform_SetCallback(ANativeWindow* window, |
| ANativeWindow_performFn perform, |
| void* data, |
| int operation, |
| va_list args) { |
| if (operation == NATIVE_WINDOW_API_SET_ON_ACQUIRED_CALLBACK) { |
| ALOGI("Intercepted NATIVE_WINDOW_API_SET_ON_ACQUIRED_CALLBACK"); |
| if (data) { |
| va_list args_copy; |
| va_copy(args_copy, args); |
| auto* captured = static_cast<OnAcquiredCallbackData*>(data); |
| captured->callback = va_arg(args_copy, ANativeWindow_OnAcquiredCallback); |
| captured->data = va_arg(args_copy, void*); |
| va_end(args_copy); |
| } |
| } |
| |
| return perform(window, operation, args); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, SurfaceDestroyedBeforeOnAcquiredCallback) { |
| // Verify that the callback from the consumer (AImageReader) doesn't cause a |
| // crash if the surface has already been destroyed. |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME, |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME, |
| }; |
| OnAcquiredCallbackData callbackData; |
| |
| createVulkanInstance(instanceLayers); |
| // Don't set listener, we will acquire manually |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 1, false); |
| getANativeWindowFromReader(); |
| |
| |
| int result = mWindow->perform( |
| mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_SetCallback, &callbackData); |
| ASSERT_EQ(result, 0); |
| |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool presentId2Supported = false; |
| bool presentWait2Supported = false; |
| bool presentTimingSupported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME) == 0) { |
| presentId2Supported = true; |
| } |
| if (strcmp(extension.extensionName, |
| VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME) == 0) { |
| presentWait2Supported = true; |
| } |
| if (strcmp(extension.extensionName, |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME) == 0) { |
| presentTimingSupported = true; |
| } |
| } |
| |
| if (!presentId2Supported || !presentWait2Supported || |
| !presentTimingSupported) { |
| GTEST_SKIP() << "VK_KHR_present_id2, VK_KHR_present_wait2, or " |
| "VK_EXT_present_timing not supported"; |
| } |
| |
| VkPhysicalDevicePresentWait2FeaturesKHR presentWait2Features = {}; |
| presentWait2Features.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_2_FEATURES_KHR; |
| presentWait2Features.presentWait2 = VK_TRUE; |
| |
| VkPhysicalDevicePresentId2FeaturesKHR presentId2Features = {}; |
| presentId2Features.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR; |
| presentId2Features.presentId2 = VK_TRUE; |
| presentId2Features.pNext = &presentWait2Features; |
| |
| VkPhysicalDevicePresentTimingFeaturesEXT presentTimingFeatures = {}; |
| presentTimingFeatures.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT; |
| presentTimingFeatures.presentTiming = VK_TRUE; |
| presentTimingFeatures.presentAtAbsoluteTime = VK_TRUE; |
| presentTimingFeatures.pNext = &presentId2Features; |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions, |
| &presentTimingFeatures); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| swapchainInfo.flags = VK_SWAPCHAIN_CREATE_PRESENT_WAIT_2_BIT_KHR; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| uint64_t presentId = 1; |
| VkPresentId2KHR presentIdInfo = {}; |
| presentIdInfo.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo.swapchainCount = 1; |
| presentIdInfo.pPresentIds = &presentId; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentIdInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &mSwapchain; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| |
| // Destroy the swapchain and surface immediately after present |
| vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr); |
| mSwapchain = VK_NULL_HANDLE; |
| vkDestroySurfaceKHR(mVkInstance, mSurface, nullptr); |
| mSurface = VK_NULL_HANDLE; |
| |
| // Trigger the callback manually (AImageReader acquire). |
| // This used to crash if the surface was already destroyed. |
| callbackData.callback(0 /*bufferId*/, 0 /*frameId*/, callbackData.data); |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace |
| |
| namespace { |
| |
| int Hook_ANativeWindow_Perform_ErrorTimestamps(ANativeWindow* window, |
| ANativeWindow_performFn perform, |
| void*, |
| int operation, |
| va_list args) { |
| if (operation == NATIVE_WINDOW_GET_FRAME_TIMESTAMPS) { |
| return -EINVAL; |
| } |
| return perform(window, operation, args); |
| } |
| |
| int Hook_ANativeWindow_Perform_SuccessTimestamps_NonZero( |
| ANativeWindow* window, |
| ANativeWindow_performFn perform, |
| void*, |
| int operation, |
| va_list args) { |
| if (operation == NATIVE_WINDOW_GET_FRAME_TIMESTAMPS) { |
| uint64_t frameId = va_arg(args, uint64_t); |
| int64_t* outRequestedPresentTime = va_arg(args, int64_t*); |
| int64_t* outAcquireTime = va_arg(args, int64_t*); |
| int64_t* outLatchTime = va_arg(args, int64_t*); |
| int64_t* outFirstRefreshStartTime = va_arg(args, int64_t*); |
| int64_t* outLastRefreshStartTime = va_arg(args, int64_t*); |
| int64_t* outGpuCompositionDoneTime = va_arg(args, int64_t*); |
| int64_t* outDisplayPresentTime = va_arg(args, int64_t*); |
| int64_t* outDequeueReadyTime = va_arg(args, int64_t*); |
| int64_t* outReleaseTime = va_arg(args, int64_t*); |
| |
| if (outRequestedPresentTime) |
| *outRequestedPresentTime = 12345; |
| if (outAcquireTime) |
| *outAcquireTime = 12345; |
| if (outLatchTime) |
| *outLatchTime = 12345; |
| if (outFirstRefreshStartTime) |
| *outFirstRefreshStartTime = 12345; |
| if (outLastRefreshStartTime) |
| *outLastRefreshStartTime = 12345; |
| if (outGpuCompositionDoneTime) |
| *outGpuCompositionDoneTime = 12345; |
| if (outDisplayPresentTime) |
| *outDisplayPresentTime = 12345; |
| if (outDequeueReadyTime) |
| *outDequeueReadyTime = 12345; |
| if (outReleaseTime) |
| *outReleaseTime = 12345; |
| |
| return 0; |
| } |
| |
| return perform(window, operation, args); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, ExtPresentTimingErrorTimestamps) { |
| // verify we return 0 timestamps when NATIVE_WINDOW_GET_FRAME_TIMESTAMPS |
| // returns an error. |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| // Check if the required display timing extension is supported by the device |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool timingExtSupported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME) == 0) { |
| timingExtSupported = true; |
| break; |
| } |
| } |
| |
| if (!timingExtSupported) { |
| GTEST_SKIP() << "Vulkan extension " |
| << VK_EXT_PRESENT_TIMING_EXTENSION_NAME |
| << " not supported."; |
| } |
| |
| VkPhysicalDevicePresentTimingFeaturesEXT presentTimingFeatures = {}; |
| presentTimingFeatures.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT; |
| presentTimingFeatures.presentTiming = VK_TRUE; |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions, |
| &presentTimingFeatures); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| // Set error interceptor |
| int result = mWindow->perform( |
| mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_ErrorTimestamps, nullptr /* data */); |
| ASSERT_EQ(result, 0); |
| |
| // Create Swapchain 1 |
| VkSwapchainKHR swapchain1 = VK_NULL_HANDLE; |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &swapchain1); |
| VK_CHECK(res); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, swapchain1, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| VkPresentTimingInfoEXT presentTimingInfo = {}; |
| presentTimingInfo.presentStageQueries = |
| VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT; |
| presentTimingInfo.targetTime = 0; |
| |
| VkPresentTimingsInfoEXT presentTimingsInfo = {}; |
| presentTimingsInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT; |
| presentTimingsInfo.swapchainCount = 1; |
| presentTimingsInfo.pTimingInfos = &presentTimingInfo; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentTimingsInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &swapchain1; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| |
| auto pfnGetPastPresentationTimingEXT = |
| (PFN_vkGetPastPresentationTimingEXT)vkGetDeviceProcAddr( |
| mDevice, "vkGetPastPresentationTimingEXT"); |
| ASSERT_NE(pfnGetPastPresentationTimingEXT, |
| (PFN_vkGetPastPresentationTimingEXT) nullptr); |
| |
| // Query timing on Swapchain 1 |
| VkPastPresentationTimingInfoEXT pastTimingInfo1 = {}; |
| pastTimingInfo1.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT; |
| pastTimingInfo1.swapchain = swapchain1; |
| |
| VkPastPresentationTimingPropertiesEXT pastTimingProps1 = {}; |
| pastTimingProps1.sType = |
| VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT; |
| |
| // First call to get count |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastTimingInfo1, |
| &pastTimingProps1); |
| VK_CHECK(res); |
| ASSERT_EQ(pastTimingProps1.presentationTimingCount, 1U); |
| |
| std::vector<VkPastPresentationTimingEXT> pastTimings1(1); |
| std::vector<VkPresentStageTimeEXT> stages1(1); |
| pastTimings1[0].sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT; |
| pastTimings1[0].pPresentStages = stages1.data(); |
| pastTimingProps1.pPresentationTimings = pastTimings1.data(); |
| |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastTimingInfo1, |
| &pastTimingProps1); |
| VK_CHECK(res); |
| |
| // Verify timestamps are 0 since interceptor returned error |
| EXPECT_EQ(pastTimings1[0].presentStageCount, 1U); |
| EXPECT_EQ(stages1[0].time, 0U); |
| |
| mSwapchain = swapchain1; |
| cleanUpSwapchainForTest(); |
| } |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, |
| ExtPresentTimingSuccessTimestampsRetiredSwapchain) { |
| // verify we return 0 timestamps when it returns success (with non-zero |
| // values) but the swapchain is retired. |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3); |
| getANativeWindowFromReader(); |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| // Check if the required display timing extension is supported by the device |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| bool timingExtSupported = false; |
| for (const auto& extension : availableExtensions) { |
| if (strcmp(extension.extensionName, |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME) == 0) { |
| timingExtSupported = true; |
| break; |
| } |
| } |
| |
| if (!timingExtSupported) { |
| GTEST_SKIP() << "Vulkan extension " |
| << VK_EXT_PRESENT_TIMING_EXTENSION_NAME |
| << " not supported."; |
| } |
| |
| VkPhysicalDevicePresentTimingFeaturesEXT presentTimingFeatures = {}; |
| presentTimingFeatures.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT; |
| presentTimingFeatures.presentTiming = VK_TRUE; |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions, |
| &presentTimingFeatures); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| // Set success interceptor |
| int result = |
| mWindow->perform(mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_SuccessTimestamps_NonZero, |
| nullptr /* data */); |
| ASSERT_EQ(result, 0); |
| |
| // Create Swapchain 1 |
| VkSwapchainKHR swapchain1 = VK_NULL_HANDLE; |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &swapchain1); |
| VK_CHECK(res); |
| |
| uint32_t imageIndex; |
| res = vkAcquireNextImageKHR(mDevice, swapchain1, UINT64_MAX, VK_NULL_HANDLE, |
| VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| VkPresentTimingInfoEXT presentTimingInfo = {}; |
| presentTimingInfo.presentStageQueries = |
| VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT; |
| presentTimingInfo.targetTime = 0; |
| |
| VkPresentTimingsInfoEXT presentTimingsInfo = {}; |
| presentTimingsInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT; |
| presentTimingsInfo.swapchainCount = 1; |
| presentTimingsInfo.pTimingInfos = &presentTimingInfo; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentTimingsInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &swapchain1; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| |
| // Retire Swapchain 1 with Swapchain 2 |
| VkSwapchainKHR swapchain2 = VK_NULL_HANDLE; |
| swapchainInfo.oldSwapchain = swapchain1; |
| res = vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &swapchain2); |
| VK_CHECK(res); |
| |
| auto pfnGetPastPresentationTimingEXT = |
| (PFN_vkGetPastPresentationTimingEXT)vkGetDeviceProcAddr( |
| mDevice, "vkGetPastPresentationTimingEXT"); |
| ASSERT_NE(pfnGetPastPresentationTimingEXT, |
| (PFN_vkGetPastPresentationTimingEXT) nullptr); |
| |
| // Query timing on Swapchain 1 (it is retired, interceptor returns 12345, so |
| // should be forced to 0) |
| VkPastPresentationTimingInfoEXT pastTimingInfo1 = {}; |
| pastTimingInfo1.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT; |
| pastTimingInfo1.swapchain = swapchain1; |
| |
| VkPastPresentationTimingPropertiesEXT pastTimingProps1 = {}; |
| pastTimingProps1.sType = |
| VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT; |
| |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastTimingInfo1, |
| &pastTimingProps1); |
| VK_CHECK(res); |
| ASSERT_EQ(pastTimingProps1.presentationTimingCount, 1U); |
| |
| std::vector<VkPastPresentationTimingEXT> pastTimings1(1); |
| std::vector<VkPresentStageTimeEXT> stages1(1); |
| pastTimings1[0].sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT; |
| pastTimings1[0].pPresentStages = stages1.data(); |
| pastTimingProps1.pPresentationTimings = pastTimings1.data(); |
| |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastTimingInfo1, |
| &pastTimingProps1); |
| VK_CHECK(res); |
| |
| EXPECT_EQ(pastTimings1[0].presentStageCount, 1U); |
| EXPECT_EQ(stages1[0].time, 0U); |
| |
| // Clean up |
| vkDestroySwapchainKHR(mDevice, swapchain1, nullptr); |
| mSwapchain = swapchain2; |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace |
| |
| TEST_F(AImageReaderVulkanSwapchainTest, |
| GetPastPresentationTimingEXT_IncompleteQueryTest) { |
| // verify that returning VK_INCOMPLETE does not erase all ready timings |
| std::vector<const char*> instanceLayers = {}; |
| std::vector<const char*> deviceLayers = {}; |
| std::vector<const char*> deviceExtensions = { |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| VK_EXT_PRESENT_TIMING_EXTENSION_NAME, |
| VK_KHR_PRESENT_ID_2_EXTENSION_NAME, |
| }; |
| |
| createVulkanInstance(instanceLayers); |
| createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 4); |
| getANativeWindowFromReader(); |
| |
| int result = mWindow->perform( |
| mWindow, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, |
| Hook_ANativeWindow_Perform_FakeTimestamps, nullptr /* data */); |
| ASSERT_EQ(result, 0); |
| |
| createVulkanSurface(); |
| pickPhysicalDeviceAndQueueFamily(); |
| |
| uint32_t extensionCount = 0; |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| nullptr); |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); |
| vkEnumerateDeviceExtensionProperties(mPhysicalDev, nullptr, &extensionCount, |
| availableExtensions.data()); |
| |
| VkPhysicalDevicePresentId2FeaturesKHR presentId2Features = {}; |
| presentId2Features.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR; |
| presentId2Features.presentId2 = VK_TRUE; |
| presentId2Features.pNext = nullptr; |
| |
| VkPhysicalDevicePresentTimingFeaturesEXT presentTimingFeatures = {}; |
| presentTimingFeatures.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT; |
| presentTimingFeatures.presentTiming = VK_TRUE; |
| presentTimingFeatures.presentAtAbsoluteTime = VK_TRUE; |
| presentTimingFeatures.pNext = &presentId2Features; |
| |
| createDeviceAndGetQueue(deviceLayers, deviceExtensions, |
| &presentTimingFeatures); |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps{}; |
| VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDev, mSurface, |
| &surfaceCaps)); |
| |
| uint32_t imageCount = surfaceCaps.minImageCount + 1; |
| if (surfaceCaps.maxImageCount > 0 && |
| imageCount > surfaceCaps.maxImageCount) { |
| imageCount = surfaceCaps.maxImageCount; |
| } |
| |
| VkSwapchainCreateInfoKHR swapchainInfo{}; |
| swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapchainInfo.surface = mSurface; |
| swapchainInfo.minImageCount = imageCount; |
| swapchainInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; |
| swapchainInfo.imageExtent = surfaceCaps.currentExtent; |
| swapchainInfo.imageArrayLayers = 1; |
| swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapchainInfo.preTransform = surfaceCaps.currentTransform; |
| swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; |
| swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| swapchainInfo.clipped = VK_TRUE; |
| |
| VkResult res = |
| vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain); |
| VK_CHECK(res); |
| ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE); |
| |
| typedef VkResult(VKAPI_PTR * PFN_vkGetPastPresentationTimingEXT_Local)( |
| VkDevice, const VkPastPresentationTimingInfoEXT*, |
| VkPastPresentationTimingPropertiesEXT*); |
| auto pfnGetPastPresentationTimingEXT = |
| reinterpret_cast<PFN_vkGetPastPresentationTimingEXT_Local>( |
| vkGetDeviceProcAddr(mDevice, "vkGetPastPresentationTimingEXT")); |
| ASSERT_NE(pfnGetPastPresentationTimingEXT, nullptr); |
| |
| // Queue 2 presents |
| for (uint64_t i = 1; i <= 2; ++i) { |
| uint32_t imageIndex; |
| res = |
| vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, |
| VK_NULL_HANDLE, VK_NULL_HANDLE, &imageIndex); |
| VK_CHECK(res); |
| |
| VkPresentTimingInfoEXT presentTimingInfo = {}; |
| presentTimingInfo.presentStageQueries = |
| VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT | |
| VK_PRESENT_STAGE_REQUEST_DEQUEUED_BIT_EXT; |
| presentTimingInfo.targetTime = 1000 * i; |
| |
| VkPresentTimingsInfoEXT presentTimingsInfo = {}; |
| presentTimingsInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT; |
| presentTimingsInfo.swapchainCount = 1; |
| presentTimingsInfo.pTimingInfos = &presentTimingInfo; |
| |
| VkPresentId2KHR presentIdInfo = {}; |
| presentIdInfo.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR; |
| presentIdInfo.swapchainCount = 1; |
| presentIdInfo.pPresentIds = &i; |
| presentIdInfo.pNext = &presentTimingsInfo; |
| |
| VkPresentInfoKHR presentInfo = {}; |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presentInfo.pNext = &presentIdInfo; |
| presentInfo.swapchainCount = 1; |
| presentInfo.pSwapchains = &mSwapchain; |
| presentInfo.pImageIndices = &imageIndex; |
| |
| res = vkQueuePresentKHR(mPresentQueue, &presentInfo); |
| VK_CHECK(res); |
| } |
| |
| // Now query with count = 1 |
| VkPastPresentationTimingInfoEXT pastInfo = {}; |
| pastInfo.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT; |
| pastInfo.swapchain = mSwapchain; |
| |
| VkPastPresentationTimingPropertiesEXT pastProps = {}; |
| pastProps.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT; |
| |
| // First query just the count |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastInfo, &pastProps); |
| VK_CHECK(res); |
| ASSERT_EQ(pastProps.presentationTimingCount, 2); |
| |
| // Now query with 1 element array |
| VkPastPresentationTimingEXT timing1[1] = {}; |
| timing1[0].sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT; |
| |
| VkPresentStageTimeEXT stages1[2] = {}; |
| timing1[0].pPresentStages = stages1; |
| |
| pastProps.presentationTimingCount = 1; |
| pastProps.pPresentationTimings = timing1; |
| |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastInfo, &pastProps); |
| ASSERT_EQ(res, VK_INCOMPLETE); |
| ASSERT_EQ(pastProps.presentationTimingCount, 1); |
| EXPECT_EQ(timing1[0].presentId, 1); |
| |
| // Query remaining elements |
| pastProps.pPresentationTimings = nullptr; |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastInfo, &pastProps); |
| VK_CHECK(res); |
| ASSERT_EQ(pastProps.presentationTimingCount, 1); |
| |
| VkPastPresentationTimingEXT timing2[1] = {}; |
| timing2[0].sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT; |
| |
| VkPresentStageTimeEXT stages2[2] = {}; |
| timing2[0].pPresentStages = stages2; |
| |
| pastProps.presentationTimingCount = 1; |
| pastProps.pPresentationTimings = timing2; |
| |
| res = pfnGetPastPresentationTimingEXT(mDevice, &pastInfo, &pastProps); |
| VK_CHECK(res); |
| ASSERT_EQ(pastProps.presentationTimingCount, 1); |
| EXPECT_EQ(timing2[0].presentId, 2); |
| |
| cleanUpSwapchainForTest(); |
| } |
| |
| } // namespace libvulkantest |
| |
| } // namespace android |