blob: f7284435a030c2e63de3ae41ed6556bb89b5a193 [file]
/*
* 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