// Copyright (C) 2018 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 #include "GoldfishOpenglTestEnv.h" #include "GrallocDispatch.h" #include "GrallocUsageConversion.h" #include "AndroidVulkanDispatch.h" #include #include #include #include "android/base/ArraySize.h" #include "android/base/files/MemStream.h" #include "android/base/files/Stream.h" #include "android/base/files/PathUtils.h" #include "android/base/memory/ScopedPtr.h" #include "android/base/synchronization/ConditionVariable.h" #include "android/base/synchronization/Lock.h" #include "android/base/system/System.h" #include "android/base/threads/FunctorThread.h" #include "android/opengles.h" #include "android/snapshot/interface.h" #include "OpenglSystemCommon/HostConnection.h" #include "OpenglSystemCommon/ProcessPipe.h" #include #include #include #include #include #include using android::base::AutoLock; using android::base::ConditionVariable; using android::base::FunctorThread; using android::base::Lock; using android::base::pj; using android::base::System; namespace aemu { static constexpr int kWindowSize = 256; static android_vulkan_dispatch* vk = nullptr; class VulkanHalTest : public ::testing::Test, public ::testing::WithParamInterface { protected: static GoldfishOpenglTestEnv* testEnv; static void SetUpTestCase() { testEnv = new GoldfishOpenglTestEnv; #ifdef _WIN32 const char* libFilename = "vulkan_android.dll"; #elif defined(__APPLE__) const char* libFilename = "libvulkan_android.dylib"; #else const char* libFilename = "libvulkan_android.so"; #endif auto path = pj(System::get()->getProgramDirectory(), "lib64", libFilename); vk = load_android_vulkan_dispatch(path.c_str()); } static void TearDownTestCase() { // Cancel all host threads as well android_finishOpenglesRenderer(); delete testEnv; testEnv = nullptr; delete vk; } static constexpr kGltransportPropName = "ro.boot.qemu.gltransport"; bool usingAddressSpaceGraphics() { char value[PROPERTY_VALUE_MAX]; if (property_get( kGltransportPropName, value, "pipe") > 0) { return !strcmp("asg", value); } return false; } void SetUp() override { mProcessPipeRestarted = false; property_set(kGltransportPropName, GetParam()); printf("%s: using transport: %s\n", __func__, GetParam()); setupGralloc(); setupVulkan(); } void TearDown() override { teardownVulkan(); teardownGralloc(); } void restartProcessPipeAndHostConnection() { processPipeRestart(); cutHostConnectionUnclean(); refreshHostConnection(); } void cutHostConnectionUnclean() { HostConnection::exitUnclean(); } void setupGralloc() { auto grallocPath = pj(System::get()->getProgramDirectory(), "lib64", "gralloc.ranchu" LIBSUFFIX); load_gralloc_module(grallocPath.c_str(), &mGralloc); set_global_gralloc_module(&mGralloc); EXPECT_NE(nullptr, mGralloc.alloc_dev); EXPECT_NE(nullptr, mGralloc.alloc_module); } void teardownGralloc() { unload_gralloc_module(&mGralloc); } buffer_handle_t createTestGrallocBuffer( int usage, int format, int width, int height, int* stride_out) { buffer_handle_t buffer; int res; res = mGralloc.alloc(width, height, format, usage, &buffer, stride_out); if (res) { fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer); ::abort(); } res = mGralloc.registerBuffer(buffer); if (res) { fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer); ::abort(); } return buffer; } void destroyTestGrallocBuffer(buffer_handle_t buffer) { int res; res = mGralloc.unregisterBuffer(buffer); if (res) { fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer); ::abort(); } res = mGralloc.free(buffer); if (res) { fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer); ::abort(); } } void setupVulkan() { uint32_t extCount = 0; std::vector exts; EXPECT_EQ(VK_SUCCESS, vk->vkEnumerateInstanceExtensionProperties( nullptr, &extCount, nullptr)); exts.resize(extCount); EXPECT_EQ(VK_SUCCESS, vk->vkEnumerateInstanceExtensionProperties( nullptr, &extCount, exts.data())); bool hasGetPhysicalDeviceProperties2 = false; bool hasExternalMemoryCapabilities = false; for (const auto& prop : exts) { if (!strcmp("VK_KHR_get_physical_device_properties2", prop.extensionName)) { hasGetPhysicalDeviceProperties2 = true; } if (!strcmp("VK_KHR_external_memory_capabilities", prop.extensionName)) { hasExternalMemoryCapabilities = true; } } std::vector enabledExtensions; if (hasGetPhysicalDeviceProperties2) { enabledExtensions.push_back("VK_KHR_get_physical_device_properties2"); mInstanceHasGetPhysicalDeviceProperties2Support = true; } if (hasExternalMemoryCapabilities) { enabledExtensions.push_back("VK_KHR_external_memory_capabilities"); mInstanceHasExternalMemorySupport = true; } const char* const* enabledExtensionNames = enabledExtensions.size() > 0 ? enabledExtensions.data() : nullptr; VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO, 0, "someAppName", 1, "someEngineName", 1, VK_VERSION_1_0, }; VkInstanceCreateInfo instCi = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 0, 0, &appInfo, 0, nullptr, (uint32_t)enabledExtensions.size(), enabledExtensionNames, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateInstance(&instCi, nullptr, &mInstance)); uint32_t physdevCount = 0; std::vector physdevs; EXPECT_EQ(VK_SUCCESS, vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount, nullptr)); physdevs.resize(physdevCount); EXPECT_EQ(VK_SUCCESS, vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount, physdevs.data())); std::vector physdevsSecond(physdevCount); EXPECT_EQ(VK_SUCCESS, vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount, physdevsSecond.data())); // Check that a second call to vkEnumeratePhysicalDevices // retrieves the same physical device handles. EXPECT_EQ(physdevs, physdevsSecond); uint32_t bestPhysicalDevice = 0; bool queuesGood = false; bool hasAndroidNativeBuffer = false; bool hasExternalMemorySupport = false; for (uint32_t i = 0; i < physdevCount; ++i) { queuesGood = false; hasAndroidNativeBuffer = false; hasExternalMemorySupport = false; bool hasGetMemoryRequirements2 = false; bool hasDedicatedAllocation = false; bool hasExternalMemoryBaseExtension = false; bool hasExternalMemoryPlatformExtension = false; uint32_t queueFamilyCount = 0; std::vector queueFamilyProps; vk->vkGetPhysicalDeviceQueueFamilyProperties( physdevs[i], &queueFamilyCount, nullptr); queueFamilyProps.resize(queueFamilyCount); vk->vkGetPhysicalDeviceQueueFamilyProperties( physdevs[i], &queueFamilyCount, queueFamilyProps.data()); for (uint32_t j = 0; j < queueFamilyCount; ++j) { auto count = queueFamilyProps[j].queueCount; auto flags = queueFamilyProps[j].queueFlags; if (count > 0 && (flags & VK_QUEUE_GRAPHICS_BIT)) { bestPhysicalDevice = i; mGraphicsQueueFamily = j; queuesGood = true; break; } } uint32_t devExtCount = 0; std::vector availableDeviceExtensions; vk->vkEnumerateDeviceExtensionProperties(physdevs[i], nullptr, &devExtCount, nullptr); availableDeviceExtensions.resize(devExtCount); vk->vkEnumerateDeviceExtensionProperties( physdevs[i], nullptr, &devExtCount, availableDeviceExtensions.data()); for (uint32_t j = 0; j < devExtCount; ++j) { if (!strcmp("VK_KHR_swapchain", availableDeviceExtensions[j].extensionName)) { hasAndroidNativeBuffer = true; } if (!strcmp("VK_KHR_get_memory_requirements2", availableDeviceExtensions[j].extensionName)) { hasGetMemoryRequirements2 = true; } if (!strcmp("VK_KHR_dedicated_allocation", availableDeviceExtensions[j].extensionName)) { hasDedicatedAllocation = true; } if (!strcmp("VK_KHR_external_memory", availableDeviceExtensions[j].extensionName)) { hasExternalMemoryBaseExtension = true; } static const char* externalMemoryPlatformExtension = "VK_ANDROID_external_memory_android_hardware_buffer"; if (!strcmp(externalMemoryPlatformExtension, availableDeviceExtensions[j].extensionName)) { hasExternalMemoryPlatformExtension = true; } } hasExternalMemorySupport = (hasGetMemoryRequirements2 && hasDedicatedAllocation && hasExternalMemoryBaseExtension && hasExternalMemoryPlatformExtension); if (queuesGood && hasExternalMemorySupport) { bestPhysicalDevice = i; break; } } EXPECT_TRUE(queuesGood); EXPECT_TRUE(hasAndroidNativeBuffer); mDeviceHasExternalMemorySupport = hasExternalMemorySupport; mPhysicalDevice = physdevs[bestPhysicalDevice]; VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); bool foundHostVisibleMemoryTypeIndex = false; bool foundDeviceLocalMemoryTypeIndex = false; for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { if (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { mHostVisibleMemoryTypeIndex = i; foundHostVisibleMemoryTypeIndex = true; } if (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { mDeviceLocalMemoryTypeIndex = i; foundDeviceLocalMemoryTypeIndex = true; } if (foundHostVisibleMemoryTypeIndex && foundDeviceLocalMemoryTypeIndex) { break; } } EXPECT_TRUE( foundHostVisibleMemoryTypeIndex && foundDeviceLocalMemoryTypeIndex); EXPECT_TRUE(foundHostVisibleMemoryTypeIndex); float priority = 1.0f; VkDeviceQueueCreateInfo dqCi = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 0, 0, mGraphicsQueueFamily, 1, &priority, }; VkDeviceCreateInfo dCi = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 0, 0, 1, &dqCi, 0, nullptr, // no layers 0, nullptr, // no extensions nullptr, // no features }; std::vector externalMemoryExtensions = { "VK_KHR_get_memory_requirements2", "VK_KHR_dedicated_allocation", "VK_KHR_external_memory", "VK_ANDROID_external_memory_android_hardware_buffer", }; // Mostly for MoltenVK or any other driver that doesn't support // external memory. std::vector usefulExtensions = { "VK_KHR_get_memory_requirements2", "VK_KHR_dedicated_allocation", }; if (mDeviceHasExternalMemorySupport) { dCi.enabledExtensionCount = (uint32_t)externalMemoryExtensions.size(); dCi.ppEnabledExtensionNames = externalMemoryExtensions.data(); } else { dCi.enabledExtensionCount = (uint32_t)usefulExtensions.size(); dCi.ppEnabledExtensionNames = usefulExtensions.data(); } EXPECT_EQ(VK_SUCCESS, vk->vkCreateDevice(physdevs[bestPhysicalDevice], &dCi, nullptr, &mDevice)); vk->vkGetDeviceQueue(mDevice, mGraphicsQueueFamily, 0, &mQueue); } void teardownVulkan() { vk->vkDestroyDevice(mDevice, nullptr); vk->vkDestroyInstance(mInstance, nullptr); } void createAndroidNativeImage(buffer_handle_t* buffer_out, VkImage* image_out) { int usage = GRALLOC_USAGE_HW_RENDER; int format = HAL_PIXEL_FORMAT_RGBA_8888; int stride; buffer_handle_t buffer = createTestGrallocBuffer( usage, format, kWindowSize, kWindowSize, &stride); uint64_t producerUsage, consumerUsage; android_convertGralloc0To1Usage(usage, &producerUsage, &consumerUsage); VkNativeBufferANDROID nativeBufferInfo = { VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, nullptr, buffer, stride, format, usage, { consumerUsage, producerUsage, }, }; VkImageCreateInfo testImageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, (const void*)&nativeBufferInfo, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { kWindowSize, kWindowSize, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr /* shared queue families */, VK_IMAGE_LAYOUT_UNDEFINED, }; VkImage testAndroidNativeImage; EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr, &testAndroidNativeImage)); *buffer_out = buffer; *image_out = testAndroidNativeImage; } void destroyAndroidNativeImage(buffer_handle_t buffer, VkImage image) { vk->vkDestroyImage(mDevice, image, nullptr); destroyTestGrallocBuffer(buffer); } AHardwareBuffer* allocateAndroidHardwareBuffer( int width = kWindowSize, int height = kWindowSize, AHardwareBuffer_Format format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, uint64_t usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN) { AHardwareBuffer_Desc desc = { kWindowSize, kWindowSize, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, usage, 4, // stride ignored for allocate; don't check this }; AHardwareBuffer* buf = nullptr; AHardwareBuffer_allocate(&desc, &buf); EXPECT_NE(nullptr, buf); return buf; } void exportAllocateAndroidHardwareBuffer( VkMemoryDedicatedAllocateInfo* dedicated, VkDeviceSize allocSize, uint32_t memoryTypeIndex, VkDeviceMemory* pMemory, AHardwareBuffer** ahw) { EXPECT_TRUE(mDeviceHasExternalMemorySupport); VkExportMemoryAllocateInfo exportAi = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, dedicated, VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, }; VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &exportAi, allocSize, memoryTypeIndex, }; VkResult res = vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, pMemory); EXPECT_EQ(VK_SUCCESS, res); if (ahw) { VkMemoryGetAndroidHardwareBufferInfoANDROID getAhbInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, 0, *pMemory, }; EXPECT_EQ(VK_SUCCESS, vk->vkGetMemoryAndroidHardwareBufferANDROID( mDevice, &getAhbInfo, ahw)); } } void importAllocateAndroidHardwareBuffer( VkMemoryDedicatedAllocateInfo* dedicated, VkDeviceSize allocSize, uint32_t memoryTypeIndex, AHardwareBuffer* ahw, VkDeviceMemory* pMemory) { VkImportAndroidHardwareBufferInfoANDROID importInfo = { VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, dedicated, ahw, }; VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &importInfo, allocSize, memoryTypeIndex, }; VkDeviceMemory memory; VkResult res = vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, pMemory); EXPECT_EQ(VK_SUCCESS, res); } void createExternalImage( VkImage* pImage, uint32_t width = kWindowSize, uint32_t height = kWindowSize, VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL) { VkExternalMemoryImageCreateInfo extMemImgCi = { VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, 0, VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, }; VkImageCreateInfo testImageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, (const void*)&extMemImgCi, 0, VK_IMAGE_TYPE_2D, format, { width, height, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, tiling, VK_IMAGE_USAGE_SAMPLED_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr /* shared queue families */, VK_IMAGE_LAYOUT_UNDEFINED, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage( mDevice, &testImageCi, nullptr, pImage)); } uint32_t getFirstMemoryTypeIndexForImage(VkImage image) { VkMemoryRequirements memReqs; vk->vkGetImageMemoryRequirements( mDevice, image, &memReqs); uint32_t memoryTypeIndex = 0; EXPECT_NE(0, memReqs.memoryTypeBits); for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) { if (memReqs.memoryTypeBits & (1 << i)) { memoryTypeIndex = i; break; } } return memoryTypeIndex; } VkDeviceSize getNeededMemorySizeForImage(VkImage image) { VkMemoryRequirements memReqs; vk->vkGetImageMemoryRequirements( mDevice, image, &memReqs); return memReqs.size; } VkResult allocateTestDescriptorSetsFromExistingPool( uint32_t setsToAllocate, VkDescriptorPool pool, VkDescriptorSetLayout setLayout, VkDescriptorSet* sets_out) { std::vector setLayouts; for (uint32_t i = 0; i < setsToAllocate; ++i) { setLayouts.push_back(setLayout); } VkDescriptorSetAllocateInfo setAi = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 0, pool, setsToAllocate, setLayouts.data(), }; return vk->vkAllocateDescriptorSets( mDevice, &setAi, sets_out); } VkResult allocateTestDescriptorSets( uint32_t maxSetCount, uint32_t setsToAllocate, VkDescriptorType descriptorType, VkDescriptorPoolCreateFlags poolCreateFlags, VkDescriptorPool* pool_out, VkDescriptorSetLayout* setLayout_out, VkDescriptorSet* sets_out) { VkDescriptorPoolSize poolSize = { descriptorType, maxSetCount, }; VkDescriptorPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, maxSetCount /* maxSets */, 1 /* poolSizeCount */, &poolSize, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &poolCi, nullptr, pool_out)); VkDescriptorSetLayoutBinding binding = { 0, descriptorType, 1, VK_SHADER_STAGE_VERTEX_BIT, nullptr, }; VkDescriptorSetLayoutCreateInfo setLayoutCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0, 1, &binding, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout( mDevice, &setLayoutCi, nullptr, setLayout_out)); return allocateTestDescriptorSetsFromExistingPool( setsToAllocate, *pool_out, *setLayout_out, sets_out); } VkResult allocateImmutableSamplerDescriptorSets( uint32_t maxSetCount, uint32_t setsToAllocate, std::vector bindingImmutabilities, VkSampler* sampler_out, VkDescriptorPool* pool_out, VkDescriptorSetLayout* setLayout_out, VkDescriptorSet* sets_out) { VkSamplerCreateInfo samplerCi = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 0, 0, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT, 0.0f, VK_FALSE, 1.0f, VK_FALSE, VK_COMPARE_OP_NEVER, 0.0f, 1.0f, VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, VK_FALSE, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateSampler( mDevice, &samplerCi, nullptr, sampler_out)); VkDescriptorPoolSize poolSize = { VK_DESCRIPTOR_TYPE_SAMPLER, maxSetCount, }; VkDescriptorPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, maxSetCount /* maxSets */, 1 /* poolSizeCount */, &poolSize, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &poolCi, nullptr, pool_out)); std::vector samplerBindings; for (size_t i = 0; i < bindingImmutabilities.size(); ++i) { VkDescriptorSetLayoutBinding samplerBinding = { (uint32_t)i, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, bindingImmutabilities[i] ? sampler_out : nullptr, }; samplerBindings.push_back(samplerBinding); } VkDescriptorSetLayoutCreateInfo setLayoutCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0, (uint32_t)samplerBindings.size(), samplerBindings.data(), }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout( mDevice, &setLayoutCi, nullptr, setLayout_out)); return allocateTestDescriptorSetsFromExistingPool( setsToAllocate, *pool_out, *setLayout_out, sets_out); } struct gralloc_implementation mGralloc; bool mInstanceHasGetPhysicalDeviceProperties2Support = false; bool mInstanceHasExternalMemorySupport = false; bool mDeviceHasExternalMemorySupport = false; bool mProcessPipeRestarted = false; VkInstance mInstance; VkPhysicalDevice mPhysicalDevice; VkDevice mDevice; VkQueue mQueue; uint32_t mHostVisibleMemoryTypeIndex; uint32_t mDeviceLocalMemoryTypeIndex; uint32_t mGraphicsQueueFamily; }; // static GoldfishOpenglTestEnv* VulkanHalTest::testEnv = nullptr; // A basic test of Vulkan HAL: // - Touch the Android loader at global, instance, and device level. TEST_P(VulkanHalTest, Basic) { } // Test: Allocate, map, flush, invalidate some host visible memory. TEST_P(VulkanHalTest, MemoryMapping) { static constexpr VkDeviceSize kTestAlloc = 16 * 1024; VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0, kTestAlloc, mHostVisibleMemoryTypeIndex, }; VkDeviceMemory mem; EXPECT_EQ(VK_SUCCESS, vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem)); void* hostPtr; EXPECT_EQ(VK_SUCCESS, vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr)); memset(hostPtr, 0xff, kTestAlloc); VkMappedMemoryRange toFlush = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0, mem, 0, kTestAlloc, }; EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush)); EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush)); for (uint32_t i = 0; i < kTestAlloc; ++i) { EXPECT_EQ(0xff, *((uint8_t*)hostPtr + i)); } int usage = GRALLOC_USAGE_HW_RENDER; int format = HAL_PIXEL_FORMAT_RGBA_8888; int stride; buffer_handle_t buffer = createTestGrallocBuffer( usage, format, kWindowSize, kWindowSize, &stride); uint64_t producerUsage, consumerUsage; android_convertGralloc0To1Usage(usage, &producerUsage, &consumerUsage); VkNativeBufferANDROID nativeBufferInfo = { VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, nullptr, buffer, stride, format, usage, { consumerUsage, producerUsage, }, }; VkImageCreateInfo testImageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, (const void*)&nativeBufferInfo, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { kWindowSize, kWindowSize, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr /* shared queue families */, VK_IMAGE_LAYOUT_UNDEFINED, }; VkImage testAndroidNativeImage; EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr, &testAndroidNativeImage)); vk->vkDestroyImage(mDevice, testAndroidNativeImage, nullptr); destroyTestGrallocBuffer(buffer); vk->vkUnmapMemory(mDevice, mem); vk->vkFreeMemory(mDevice, mem, nullptr); } // Tests creation of VkImages backed by gralloc buffers. TEST_P(VulkanHalTest, AndroidNativeImageCreation) { VkImage image; buffer_handle_t buffer; createAndroidNativeImage(&buffer, &image); destroyAndroidNativeImage(buffer, image); } // Tests the path to sync Android native buffers with Gralloc buffers. TEST_P(VulkanHalTest, AndroidNativeImageQueueSignal) { VkImage image; buffer_handle_t buffer; int fenceFd; createAndroidNativeImage(&buffer, &image); PFN_vkQueueSignalReleaseImageANDROID func = (PFN_vkQueueSignalReleaseImageANDROID) vk->vkGetDeviceProcAddr(mDevice, "vkQueueSignalReleaseImageANDROID"); if (func) { fprintf(stderr, "%s: qsig\n", __func__); func(mQueue, 0, nullptr, image, &fenceFd); } destroyAndroidNativeImage(buffer, image); } // Tests VK_KHR_get_physical_device_properties2: // new API: vkGetPhysicalDeviceProperties2KHR TEST_P(VulkanHalTest, GetPhysicalDeviceProperties2) { if (!mInstanceHasGetPhysicalDeviceProperties2Support) { printf("Warning: Not testing VK_KHR_physical_device_properties2, not " "supported\n"); return; } PFN_vkGetPhysicalDeviceProperties2KHR physProps2KHRFunc = (PFN_vkGetPhysicalDeviceProperties2KHR)vk->vkGetInstanceProcAddr( mInstance, "vkGetPhysicalDeviceProperties2KHR"); EXPECT_NE(nullptr, physProps2KHRFunc); VkPhysicalDeviceProperties2KHR props2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, 0, }; physProps2KHRFunc(mPhysicalDevice, &props2); VkPhysicalDeviceProperties props; vk->vkGetPhysicalDeviceProperties(mPhysicalDevice, &props); EXPECT_EQ(props.vendorID, props2.properties.vendorID); EXPECT_EQ(props.deviceID, props2.properties.deviceID); } // Tests VK_KHR_get_physical_device_properties2: // new API: vkGetPhysicalDeviceFeatures2KHR TEST_P(VulkanHalTest, GetPhysicalDeviceFeatures2KHR) { if (!mInstanceHasGetPhysicalDeviceProperties2Support) { printf("Warning: Not testing VK_KHR_physical_device_properties2, not " "supported\n"); return; } PFN_vkGetPhysicalDeviceFeatures2KHR physDeviceFeatures = (PFN_vkGetPhysicalDeviceFeatures2KHR)vk->vkGetInstanceProcAddr( mInstance, "vkGetPhysicalDeviceFeatures2KHR"); EXPECT_NE(nullptr, physDeviceFeatures); VkPhysicalDeviceFeatures2 features2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, 0, }; physDeviceFeatures(mPhysicalDevice, &features2); } // Tests VK_KHR_get_physical_device_properties2: // new API: vkGetPhysicalDeviceImageFormatProperties2KHR TEST_P(VulkanHalTest, GetPhysicalDeviceImageFormatProperties2KHR) { if (!mInstanceHasGetPhysicalDeviceProperties2Support) { printf("Warning: Not testing VK_KHR_physical_device_properties2, not " "supported\n"); return; } PFN_vkGetPhysicalDeviceImageFormatProperties2KHR physDeviceImageFormatPropertiesFunc = (PFN_vkGetPhysicalDeviceImageFormatProperties2KHR) vk->vkGetInstanceProcAddr(mInstance, "vkGetPhysicalDeviceImageForm" "atProperties2KHR"); EXPECT_NE(nullptr, physDeviceImageFormatPropertiesFunc); VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, 0, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_SAMPLED_BIT, 0, }; VkImageFormatProperties2 res = { VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, 0, }; EXPECT_EQ(VK_SUCCESS, physDeviceImageFormatPropertiesFunc( mPhysicalDevice, &imageFormatInfo, &res)); } // Tests that if we create an instance and the API version is less than 1.1, // we return null for 1.1 core API calls. TEST_P(VulkanHalTest, Hide1_1FunctionPointers) { VkPhysicalDeviceProperties props; vk->vkGetPhysicalDeviceProperties(mPhysicalDevice, &props); if (props.apiVersion < VK_API_VERSION_1_1) { EXPECT_EQ(nullptr, vk->vkGetDeviceProcAddr(mDevice, "vkTrimCommandPool")); } else { EXPECT_NE(nullptr, vk->vkGetDeviceProcAddr(mDevice, "vkTrimCommandPool")); } } // Tests VK_ANDROID_external_memory_android_hardware_buffer's allocation API. // The simplest: export allocate device local memory. // Disabled for now: currently goes down invalid paths in the GL side TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_ExportDeviceLocal) { if (!mDeviceHasExternalMemorySupport) return; VkDeviceMemory memory; AHardwareBuffer* ahw; exportAllocateAndroidHardwareBuffer( nullptr, 4096, mDeviceLocalMemoryTypeIndex, &memory, &ahw); vk->vkFreeMemory(mDevice, memory, nullptr); } // Test AHB allocation via import. // Disabled for now: currently goes down invalid paths in the GL side TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_ImportDeviceLocal) { if (!mDeviceHasExternalMemorySupport) return; AHardwareBuffer* testBuf = allocateAndroidHardwareBuffer(); VkDeviceMemory memory; importAllocateAndroidHardwareBuffer( nullptr, 4096, // also checks that the top-level allocation size is ignored mDeviceLocalMemoryTypeIndex, testBuf, &memory); vk->vkFreeMemory(mDevice, memory, nullptr); AHardwareBuffer_release(testBuf); } // Test AHB allocation via export, but with a dedicated allocation (image). // Disabled for now: currently goes down invalid paths in the GL side TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_Dedicated_Export) { if (!mDeviceHasExternalMemorySupport) return; VkImage testAhbImage; createExternalImage(&testAhbImage); VkMemoryDedicatedAllocateInfo dedicatedAi = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, 0, testAhbImage, VK_NULL_HANDLE, }; VkDeviceMemory memory; AHardwareBuffer* buffer; exportAllocateAndroidHardwareBuffer( &dedicatedAi, 4096, getFirstMemoryTypeIndexForImage(testAhbImage), &memory, &buffer); EXPECT_EQ(VK_SUCCESS, vk->vkBindImageMemory(mDevice, testAhbImage, memory, 0)); vk->vkFreeMemory(mDevice, memory, nullptr); vk->vkDestroyImage(mDevice, testAhbImage, nullptr); } // Test AHB allocation via import, but with a dedicated allocation (image). // Disabled for now: currently goes down invalid paths in the GL side TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_Dedicated_Import) { if (!mDeviceHasExternalMemorySupport) return; AHardwareBuffer* testBuf = allocateAndroidHardwareBuffer(); VkImage testAhbImage; createExternalImage(&testAhbImage); VkMemoryDedicatedAllocateInfo dedicatedAi = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, 0, testAhbImage, VK_NULL_HANDLE, }; VkDeviceMemory memory; importAllocateAndroidHardwareBuffer( &dedicatedAi, 4096, // also checks that the top-level allocation size is ignored getFirstMemoryTypeIndexForImage(testAhbImage), testBuf, &memory); EXPECT_EQ(VK_SUCCESS, vk->vkBindImageMemory(mDevice, testAhbImage, memory, 0)); vk->vkFreeMemory(mDevice, memory, nullptr); vk->vkDestroyImage(mDevice, testAhbImage, nullptr); AHardwareBuffer_release(testBuf); } // Test many host visible allocations. TEST_P(VulkanHalTest, HostVisibleAllocations) { static constexpr VkDeviceSize kTestAllocSizesSmall[] = { 4, 5, 6, 16, 32, 37, 64, 255, 256, 267, 1024, 1023, 1025, 4095, 4096, 4097, 16333, }; static constexpr size_t kNumSmallAllocSizes = android::base::arraySize(kTestAllocSizesSmall); static constexpr size_t kNumTrialsSmall = 1000; static constexpr VkDeviceSize kTestAllocSizesLarge[] = { 1048576, 1048577, 1048575 }; static constexpr size_t kNumLargeAllocSizes = android::base::arraySize(kTestAllocSizesLarge); static constexpr size_t kNumTrialsLarge = 20; static constexpr float kLargeAllocChance = 0.05; std::default_random_engine generator; // Use a consistent seed value to avoid flakes generator.seed(0); std::uniform_int_distribution smallAllocIndexDistribution(0, kNumSmallAllocSizes - 1); std::uniform_int_distribution largeAllocIndexDistribution(0, kNumLargeAllocSizes - 1); std::bernoulli_distribution largeAllocDistribution(kLargeAllocChance); size_t smallAllocCount = 0; size_t largeAllocCount = 0; VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0, 0, mHostVisibleMemoryTypeIndex, }; std::vector allocs; while (smallAllocCount < kNumTrialsSmall || largeAllocCount < kNumTrialsLarge) { VkDeviceMemory mem = VK_NULL_HANDLE; void* hostPtr = nullptr; if (largeAllocDistribution(generator)) { if (largeAllocCount < kNumTrialsLarge) { fprintf(stderr, "%s: large alloc\n", __func__); allocInfo.allocationSize = kTestAllocSizesLarge[ largeAllocIndexDistribution(generator)]; ++largeAllocCount; } } else { if (smallAllocCount < kNumTrialsSmall) { allocInfo.allocationSize = kTestAllocSizesSmall[ smallAllocIndexDistribution(generator)]; ++smallAllocCount; } } EXPECT_EQ(VK_SUCCESS, vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem)); if (!mem) continue; allocs.push_back(mem); EXPECT_EQ(VK_SUCCESS, vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr)); memset(hostPtr, 0xff, 4); VkMappedMemoryRange toFlush = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0, mem, 0, 4, }; EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush)); EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush)); for (uint32_t i = 0; i < 4; ++i) { EXPECT_EQ(0xff, *((uint8_t*)hostPtr + i)); } } for (auto mem : allocs) { vk->vkUnmapMemory(mDevice, mem); vk->vkFreeMemory(mDevice, mem, nullptr); } } TEST_P(VulkanHalTest, BufferCreate) { VkBufferCreateInfo bufCi = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0, 4096, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, }; VkBuffer buffer; vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffer); VkMemoryRequirements memReqs; vk->vkGetBufferMemoryRequirements(mDevice, buffer, &memReqs); vk->vkDestroyBuffer(mDevice, buffer, nullptr); } TEST_P(VulkanHalTest, SnapshotSaveLoad) { // TODO: Skip if using address space graphics if (usingAddressSpaceGraphics()) { printf("%s: skipping, ASG does not yet support snapshots\n", __func__); return; } androidSnapshot_save("test_snapshot"); androidSnapshot_load("test_snapshot"); } TEST_P(VulkanHalTest, SnapshotSaveLoadSimpleNonDispatchable) { // TODO: Skip if using address space graphics if (usingAddressSpaceGraphics()) { printf("%s: skipping, ASG does not yet support snapshots\n", __func__); return; } VkBufferCreateInfo bufCi = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0, 4096, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, }; VkFence fence; VkFenceCreateInfo fenceCi = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 0, }; vk->vkCreateFence(mDevice, &fenceCi, nullptr, &fence); fprintf(stderr, "%s: guest fence: %p\n", __func__, fence); VkBuffer buffer; vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffer); fprintf(stderr, "%s: guest buffer: %p\n", __func__, buffer); androidSnapshot_save("test_snapshot"); androidSnapshot_load("test_snapshot"); VkMemoryRequirements memReqs; vk->vkGetBufferMemoryRequirements(mDevice, buffer, &memReqs); vk->vkDestroyBuffer(mDevice, buffer, nullptr); vk->vkDestroyFence(mDevice, fence, nullptr); } // Tests save/load of host visible memory. This is not yet a viable host-only // test because, the only way to really test it is to be able to preserve a // host visible address for the simulated guest while the backing memory under // it changes due to the new snapshot. In other words, this is arbitrary // remapping of virtual addrs and is functionality that does not exist on // Linux/macOS. It would ironically require a hypervisor (or an OS that // supports freer ways of mapping memory) in order to test properly. // Disabled for now: currently goes down invalid paths in the GL side TEST_P(VulkanHalTest, DISABLED_SnapshotSaveLoadHostVisibleMemory) { // TODO: Skip if using address space graphics if (usingAddressSpaceGraphics()) { printf("%s: skipping, ASG does not yet support snapshots\n", __func__); return; } static constexpr VkDeviceSize kTestAlloc = 16 * 1024; VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0, kTestAlloc, mHostVisibleMemoryTypeIndex, }; VkDeviceMemory mem; EXPECT_EQ(VK_SUCCESS, vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem)); void* hostPtr; EXPECT_EQ(VK_SUCCESS, vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr)); androidSnapshot_save("test_snapshot"); androidSnapshot_load("test_snapshot"); memset(hostPtr, 0xff, kTestAlloc); VkMappedMemoryRange toFlush = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0, mem, 0, kTestAlloc, }; EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush)); EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush)); vk->vkUnmapMemory(mDevice, mem); vk->vkFreeMemory(mDevice, mem, nullptr); } // Tests save/load of a dispatchable handle, such as VkCommandBuffer. // Note that the internal state of the command buffer is not snapshotted yet. TEST_P(VulkanHalTest, SnapshotSaveLoadSimpleDispatchable) { // TODO: Skip if using address space graphics if (usingAddressSpaceGraphics()) { printf("%s: skipping, ASG does not yet support snapshots\n", __func__); return; } VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool pool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &pool); VkCommandBuffer cb; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, &cb); androidSnapshot_save("test_snapshot"); androidSnapshot_load("test_snapshot"); vk->vkFreeCommandBuffers(mDevice, pool, 1, &cb); vk->vkDestroyCommandPool(mDevice, pool, nullptr); } // Tests that dependencies are respected between different handle types, // such as VkImage and VkImageView. TEST_P(VulkanHalTest, SnapshotSaveLoadDependentHandlesImageView) { // TODO: Skip if using address space graphics if (usingAddressSpaceGraphics()) { printf("%s: skipping, ASG does not yet support snapshots\n", __func__); return; } VkImage image; VkImageCreateInfo imageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 0, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { 1, 1, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, VK_IMAGE_LAYOUT_UNDEFINED, }; vk->vkCreateImage(mDevice, &imageCi, nullptr, &image); VkImageView imageView; VkImageViewCreateInfo imageViewCi = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 0, 0, image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, }, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, }, }; vk->vkCreateImageView(mDevice, &imageViewCi, nullptr, &imageView); androidSnapshot_save("test_snapshot"); androidSnapshot_load("test_snapshot"); vk->vkDestroyImageView(mDevice, imageView, nullptr); vk->vkCreateImageView(mDevice, &imageViewCi, nullptr, &imageView); vk->vkDestroyImageView(mDevice, imageView, nullptr); vk->vkDestroyImage(mDevice, image, nullptr); } // Tests beginning and ending command buffers from separate threads. TEST_P(VulkanHalTest, SeparateThreadCommandBufferBeginEnd) { Lock lock; ConditionVariable cvSequence; uint32_t begins = 0; uint32_t ends = 0; constexpr uint32_t kTrials = 1000; VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool pool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &pool); VkCommandBuffer cb; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, &cb); auto timeoutDeadline = []() { return System::get()->getUnixTimeUs() + 5000000; // 5 s }; FunctorThread beginThread([this, cb, &lock, &cvSequence, &begins, &ends, timeoutDeadline]() { while (begins < kTrials) { AutoLock a(lock); while (ends != begins) { if (!cvSequence.timedWait(&lock, timeoutDeadline())) { EXPECT_TRUE(false) << "Error: begin thread timed out!"; return 0; } } VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0, }; vk->vkBeginCommandBuffer(cb, &beginInfo); ++begins; cvSequence.signal(); } vk->vkDeviceWaitIdle(mDevice); return 0; }); FunctorThread endThread([this, cb, &lock, &cvSequence, &begins, &ends, timeoutDeadline]() { while (ends < kTrials) { AutoLock a(lock); while (begins - ends != 1) { if (!cvSequence.timedWait(&lock, timeoutDeadline())) { EXPECT_TRUE(false) << "Error: end thread timed out!"; return 0; } } vk->vkEndCommandBuffer(cb); ++ends; cvSequence.signal(); } vk->vkDeviceWaitIdle(mDevice); return 0; }); beginThread.start(); endThread.start(); beginThread.wait(); endThread.wait(); vk->vkFreeCommandBuffers(mDevice, pool, 1, &cb); vk->vkDestroyCommandPool(mDevice, pool, nullptr); } // Tests creating a bunch of descriptor sets and freeing them via vkFreeDescriptorSet. // 1. Via vkFreeDescriptorSet directly // 2. Via vkResetDescriptorPool // 3. Via vkDestroyDescriptorPool // 4. Via vkResetDescriptorPool and double frees in vkFreeDescriptorSet // 5. Via vkResetDescriptorPool and double frees in vkFreeDescriptorSet // 4. Via vkResetDescriptorPool, creating more, and freeing vai vkFreeDescriptorSet // (because vkFree* APIs are expected to never fail) // https://github.com/KhronosGroup/Vulkan-Docs/issues/1070 TEST_P(VulkanHalTest, DescriptorSetAllocFreeBasic) { const uint32_t kSetCount = 4; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kSetCount); EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kSetCount, kSetCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); // The double free should also work EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); // Alloc/free again should also work EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSetsFromExistingPool( kSetCount, pool, setLayout, sets.data())); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } // Tests creating a bunch of descriptor sets and freeing them via // vkResetDescriptorPool, and that vkFreeDescriptorSets still succeeds. TEST_P(VulkanHalTest, DescriptorSetAllocFreeReset) { const uint32_t kSetCount = 4; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kSetCount); EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kSetCount, kSetCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool( mDevice, pool, 0)); // The double free should also work EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); // Alloc/reset/free again should also work EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSetsFromExistingPool( kSetCount, pool, setLayout, sets.data())); EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool( mDevice, pool, 0)); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } // Tests creating a bunch of descriptor sets and freeing them via vkDestroyDescriptorPool, and that vkFreeDescriptorSets still succeeds. TEST_P(VulkanHalTest, DescriptorSetAllocFreeDestroy) { const uint32_t kSetCount = 4; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kSetCount); EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kSetCount, kSetCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kSetCount, sets.data())); } // Tests that immutable sampler descriptors properly cause // the |sampler| field of VkWriteDescriptorSet's descriptor image info // to be ignored. TEST_P(VulkanHalTest, ImmutableSamplersSuppressVkWriteDescriptorSetSampler) { const uint32_t kSetCount = 4; std::vector bindingImmutabilities = { false, true, false, false, }; VkSampler sampler; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kSetCount); EXPECT_EQ(VK_SUCCESS, allocateImmutableSamplerDescriptorSets( kSetCount * bindingImmutabilities.size(), kSetCount, bindingImmutabilities, &sampler, &pool, &setLayout, sets.data())); for (uint32_t i = 0; i < bindingImmutabilities.size(); ++i) { VkDescriptorImageInfo imageInfo = { bindingImmutabilities[i] ? (VkSampler)0xdeadbeef : sampler, 0, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[0], 1, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, &imageInfo, nullptr, nullptr, }; vk->vkUpdateDescriptorSets(mDevice, 1, &write, 0, nullptr); } vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); vk->vkDestroyDescriptorSetLayout(mDevice, setLayout, nullptr); vk->vkDestroySampler(mDevice, sampler, nullptr); } // Tests vkGetImageMemoryRequirements2 TEST_P(VulkanHalTest, GetImageMemoryRequirements2) { VkImageCreateInfo testImageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { kWindowSize, kWindowSize, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr /* shared queue families */, VK_IMAGE_LAYOUT_UNDEFINED, }; VkImage testImage; EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr, &testImage)); VkImageMemoryRequirementsInfo2 info2 = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR, 0, testImage, }; VkMemoryDedicatedRequirements dedicatedReqs { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR, 0, }; VkMemoryRequirements2 reqs2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR, 0, }; reqs2.pNext = &dedicatedReqs; PFN_vkGetImageMemoryRequirements2KHR func = (PFN_vkGetImageMemoryRequirements2KHR) vk->vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2KHR"); EXPECT_NE(nullptr, func); func(mDevice, &info2, &reqs2); vk->vkDestroyImage(mDevice, testImage, nullptr); } TEST_P(VulkanHalTest, ProcessCleanup) { static constexpr uint32_t kNumTrials = 10; static constexpr uint32_t kNumImagesPerTrial = 100; for (uint32_t i = 0; i < kNumTrials; ++i) { VkImage images[kNumImagesPerTrial]; for (uint32_t j = 0; j < kNumImagesPerTrial; ++j) { VkImageCreateInfo imageCi = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 0, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, { 1, 1, 1, }, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, VK_IMAGE_LAYOUT_UNDEFINED, }; vk->vkCreateImage(mDevice, &imageCi, nullptr, &images[j]); } for (uint32_t j = 0; j < kNumImagesPerTrial; ++j) { vk->vkDestroyImage(mDevice, images[j], nullptr); } restartProcessPipeAndHostConnection(); setupVulkan(); } } // Multithreaded benchmarks: Speed of light with simple vkCmd's. // // Currently disabled until we land // VulkanQueueSubmitWithCommands---syncEncodersFor** interferes with rc // encoders TEST_P(VulkanHalTest, DISABLED_MultithreadedSimpleCommand) { Lock lock; constexpr uint32_t kThreadCount = 4; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kThreadCount); // Setup descriptor sets EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kThreadCount, kThreadCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCi = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0, 1, &setLayout, 0, nullptr, }; vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout); // Setup command buffers VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool commandPool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool); VkCommandBuffer cbs[kThreadCount]; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs); std::vector threads; constexpr uint32_t kRecordsPerThread = 20000; constexpr uint32_t kRepeatSubmits = 1; constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits; for (uint32_t i = 0; i < kThreadCount; ++i) { FunctorThread* thread = new FunctorThread([this, &lock, cbs, sets, pipelineLayout, i]() { VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0, }; for (uint32_t k = 0; k < kRepeatSubmits; ++k) { vk->vkBeginCommandBuffer(cbs[i], &beginInfo); VkRect2D scissor = { { 0, 0, }, { 256, 256, }, }; for (uint32_t j = 0; j < kRecordsPerThread; ++j) { vk->vkCmdBindDescriptorSets( cbs[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &sets[i], 0, nullptr); } vk->vkEndCommandBuffer(cbs[i]); VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, 0, 0, 1, &cbs[i], 0, 0, }; { AutoLock queueLock(lock); vk->vkQueueSubmit(mQueue, 1, &si, 0); } } VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); return 0; }); threads.push_back(thread); } auto cpuTimeStart = System::cpuTime(); for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->start(); } for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->wait(); delete threads[i]; } vk->vkQueueWaitIdle(mQueue); vk->vkDeviceWaitIdle(mDevice); auto cpuTime = System::cpuTime() - cpuTimeStart; uint64_t duration_us = cpuTime.wall_time_us; uint64_t duration_cpu_us = cpuTime.usageUs(); float ms = duration_us / 1000.0f; float sec = duration_us / 1000000.0f; float submitHz = (float)kTotalRecords / sec; printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount); vk->vkFreeCommandBuffers(mDevice, commandPool, 1, cbs); vk->vkDestroyCommandPool(mDevice, commandPool, nullptr); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kThreadCount, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } // Multithreaded benchmarks: Round trip speed of light. // Currently disabled until we land // VulkanQueueSubmitWithCommands---syncEncodersFor** interferes with rc // encoders TEST_P(VulkanHalTest, DISABLED_MultithreadedRoundTrip) { android::base::enableTracing(); constexpr uint32_t kThreadCount = 6; std::vector threads; constexpr uint32_t kRecordsPerThread = 50; constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread; for (uint32_t i = 0; i < kThreadCount; ++i) { FunctorThread* thread = new FunctorThread([this]() { for (uint32_t j = 0; j < kRecordsPerThread; ++j) { VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); } return 0; }); threads.push_back(thread); } auto cpuTimeStart = System::cpuTime(); for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->start(); } for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->wait(); delete threads[i]; } vk->vkDeviceWaitIdle(mDevice); auto cpuTime = System::cpuTime() - cpuTimeStart; uint64_t duration_us = cpuTime.wall_time_us; uint64_t duration_cpu_us = cpuTime.usageUs(); float ms = duration_us / 1000.0f; float sec = duration_us / 1000000.0f; float submitHz = (float)kTotalRecords / sec; printf("Round trip %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount); android::base::disableTracing(); usleep(1000000); } // Secondary command buffers. TEST_P(VulkanHalTest, SecondaryCommandBuffers) { constexpr uint32_t kThreadCount = 1; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kThreadCount); // Setup descriptor sets EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kThreadCount, kThreadCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCi = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0, 1, &setLayout, 0, nullptr, }; vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout); // Setup command buffers VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool commandPool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool); VkCommandBuffer cbs[kThreadCount]; VkCommandBuffer cbs2[kThreadCount * 2]; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs); VkCommandBufferAllocateInfo cbAi2 = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2); std::vector threads; constexpr uint32_t kRecordsPerThread = 20000; constexpr uint32_t kRepeatSubmits = 1; constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits; for (uint32_t i = 0; i < kThreadCount; ++i) { FunctorThread* thread = new FunctorThread([this, cbs, cbs2, sets, pipelineLayout, i]() { VkCommandBufferInheritanceInfo inheritanceInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0, VK_NULL_HANDLE, 0, VK_NULL_HANDLE, VK_FALSE, 0, 0, }; VkCommandBufferBeginInfo secondaryBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, &inheritanceInfo, }; VkCommandBufferBeginInfo primaryBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0, }; for (uint32_t k = 0; k < kRepeatSubmits; ++k) { // Secondaries { VkCommandBuffer first = cbs2[2 * i]; VkCommandBuffer second = cbs2[2 * i + 1]; vk->vkBeginCommandBuffer(first, &secondaryBeginInfo); for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) { vk->vkCmdBindDescriptorSets( first, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &sets[i], 0, nullptr); } vk->vkEndCommandBuffer(first); vk->vkBeginCommandBuffer(second, &secondaryBeginInfo); for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) { vk->vkCmdBindDescriptorSets( second, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &sets[i], 0, nullptr); } vk->vkEndCommandBuffer(second); } vk->vkBeginCommandBuffer(cbs[i], &primaryBeginInfo); vk->vkCmdExecuteCommands(cbs[i], 2, cbs2 + 2 * i); vk->vkEndCommandBuffer(cbs[i]); VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, 0, 0, 1, &cbs[i], 0, 0, }; vk->vkQueueSubmit(mQueue, 1, &si, 0); } VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); return 0; }); threads.push_back(thread); } auto cpuTimeStart = System::cpuTime(); for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->start(); } for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->wait(); delete threads[i]; } vk->vkQueueWaitIdle(mQueue); vk->vkDeviceWaitIdle(mDevice); auto cpuTime = System::cpuTime() - cpuTimeStart; uint64_t duration_us = cpuTime.wall_time_us; uint64_t duration_cpu_us = cpuTime.usageUs(); float ms = duration_us / 1000.0f; float sec = duration_us / 1000000.0f; float submitHz = (float)kTotalRecords / sec; printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs); vk->vkDestroyCommandPool(mDevice, commandPool, nullptr); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kThreadCount, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } // Secondary command buffers. TEST_P(VulkanHalTest, SecondaryCommandBuffersWithDescriptorSetUpdate) { constexpr uint32_t kThreadCount = 1; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; // Setup descriptor sets uint32_t maxSetsTotal = 4; std::vector sets(maxSetsTotal); VkSampler sampler; VkBuffer buffers[2]; VkSamplerCreateInfo samplerCi = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 0, 0, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT, 0.0f, VK_FALSE, 1.0f, VK_FALSE, VK_COMPARE_OP_NEVER, 0.0f, 1.0f, VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, VK_FALSE, }; VkBufferCreateInfo bufCi = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0, 4096, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateSampler( mDevice, &samplerCi, nullptr, &sampler)); EXPECT_EQ(VK_SUCCESS, vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffers[0])); EXPECT_EQ(VK_SUCCESS, vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffers[1])); VkDescriptorPoolSize poolSizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLER, 1 * maxSetsTotal, }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 8 * maxSetsTotal, }, }; VkDescriptorPoolCreateInfo dpCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, maxSetsTotal /* maxSets */, 2 /* poolSizeCount */, poolSizes, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &dpCi, nullptr, &pool)); VkDescriptorSetLayoutBinding bindings[] = { { 0, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT, &sampler, }, /* immutable sampler */ { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 6, VK_SHADER_STAGE_VERTEX_BIT, nullptr, }, { 10, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, VK_SHADER_STAGE_VERTEX_BIT, nullptr, }, }; VkDescriptorSetLayoutCreateInfo setLayoutCi = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0, sizeof(bindings) / sizeof(VkDescriptorSetLayoutBinding), bindings, }; EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout( mDevice, &setLayoutCi, nullptr, &setLayout)); std::vector setLayouts(maxSetsTotal, setLayout); VkDescriptorSetAllocateInfo setAi = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 0, pool, maxSetsTotal, setLayouts.data(), }; EXPECT_EQ(VK_SUCCESS, vk->vkAllocateDescriptorSets( mDevice, &setAi, sets.data())); EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool( mDevice, pool, 0)); EXPECT_EQ(VK_SUCCESS, vk->vkAllocateDescriptorSets( mDevice, &setAi, sets.data())); VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCi = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0, 1, &setLayout, 0, nullptr, }; vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout); // Setup command buffers VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool commandPool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool); VkCommandBuffer cbs[kThreadCount]; VkCommandBuffer cbs2[kThreadCount * 2]; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs); VkCommandBufferAllocateInfo cbAi2 = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2); std::vector threads; constexpr uint32_t kRecordsPerThread = 4; constexpr uint32_t kRepeatSubmits = 2; constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits; for (uint32_t i = 0; i < kThreadCount; ++i) { FunctorThread* thread = new FunctorThread([this, maxSetsTotal, buffers, cbs, cbs2, sets, pipelineLayout, i]() { VkCommandBufferInheritanceInfo inheritanceInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0, VK_NULL_HANDLE, 0, VK_NULL_HANDLE, VK_FALSE, 0, 0, }; VkCommandBufferBeginInfo secondaryBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, &inheritanceInfo, }; VkCommandBufferBeginInfo primaryBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0, }; for (uint32_t k = 0; k < kRepeatSubmits; ++k) { // Secondaries { VkCommandBuffer first = cbs2[2 * i]; VkCommandBuffer second = cbs2[2 * i + 1]; vk->vkBeginCommandBuffer(first, &secondaryBeginInfo); for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) { for (uint32_t l = 0; l < maxSetsTotal; ++l) { VkDescriptorImageInfo immutableSamplerImageInfos[] = { { (VkSampler)0xdeadbeef, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, }, { (VkSampler)0xdeadbeef, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, }, }; VkDescriptorBufferInfo bufferInfos[] = { { buffers[0], 0, 16, }, { buffers[1], 16, 32, }, { buffers[0], 1024, 4096, }, { buffers[1], 256, 512, }, { buffers[0], 0, 512, }, { buffers[1], 8, 48, }, }; VkWriteDescriptorSet descWrites[] = { { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l], 0, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, immutableSamplerImageInfos, bufferInfos, nullptr, }, { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l], 1, 0, 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, immutableSamplerImageInfos, bufferInfos, nullptr, }, { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l], 1, 4, 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, immutableSamplerImageInfos, bufferInfos, nullptr, }, { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l], 10, 0, 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, immutableSamplerImageInfos, bufferInfos + 2, nullptr, }, }; VkCopyDescriptorSet descCopies[] = { { VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET, 0, sets[l], 10, 0, sets[l], 1, 2, 2, }, { VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET, 0, sets[l], 1, 0, sets[l], 10, 0, 2, }, }; vk->vkUpdateDescriptorSets( mDevice, sizeof(descWrites) / sizeof(VkWriteDescriptorSet), descWrites, sizeof(descCopies) / sizeof(VkCopyDescriptorSet), descCopies); } vk->vkCmdBindDescriptorSets( first, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, maxSetsTotal, &sets[0], 0, nullptr); } vk->vkEndCommandBuffer(first); vk->vkBeginCommandBuffer(second, &secondaryBeginInfo); for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) { vk->vkCmdBindDescriptorSets( second, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &sets[i], 0, nullptr); } vk->vkEndCommandBuffer(second); } vk->vkBeginCommandBuffer(cbs[i], &primaryBeginInfo); vk->vkCmdExecuteCommands(cbs[i], 2, cbs2 + 2 * i); vk->vkEndCommandBuffer(cbs[i]); VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, 0, 0, 1, &cbs[i], 0, 0, }; vk->vkQueueSubmit(mQueue, 1, &si, 0); vk->vkQueueWaitIdle(mQueue); } VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); return 0; }); threads.push_back(thread); } auto cpuTimeStart = System::cpuTime(); for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->start(); } for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->wait(); delete threads[i]; } vk->vkQueueWaitIdle(mQueue); vk->vkDeviceWaitIdle(mDevice); auto cpuTime = System::cpuTime() - cpuTimeStart; uint64_t duration_us = cpuTime.wall_time_us; uint64_t duration_cpu_us = cpuTime.usageUs(); float ms = duration_us / 1000.0f; float sec = duration_us / 1000000.0f; float submitHz = (float)kTotalRecords / sec; printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs); vk->vkDestroyCommandPool(mDevice, commandPool, nullptr); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, maxSetsTotal, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } // Flush TEST_P(VulkanHalTest, Flush) { constexpr uint32_t kThreadCount = 1; VkDescriptorPool pool; VkDescriptorSetLayout setLayout; std::vector sets(kThreadCount); // Setup descriptor sets EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets( kThreadCount, kThreadCount, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, &pool, &setLayout, sets.data())); VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCi = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0, 1, &setLayout, 0, nullptr, }; vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout); // Setup command buffers VkCommandPoolCreateInfo poolCi = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily, }; VkCommandPool commandPool; vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool); VkCommandBuffer cbs[kThreadCount]; VkCommandBuffer cbs2[kThreadCount * 2]; VkCommandBufferAllocateInfo cbAi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs); VkCommandBufferAllocateInfo cbAi2 = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0, commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2, }; vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2); std::vector threads; constexpr uint32_t kRecordsPerThread = 800000; constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread; for (uint32_t i = 0; i < kThreadCount; ++i) { FunctorThread* thread = new FunctorThread([this, cbs, cbs2, sets, pipelineLayout, i]() { for (uint32_t k = 0; k < kRecordsPerThread; ++k) { VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, 0, 0, 0, nullptr, 0, 0, }; vk->vkQueueSubmit(mQueue, 1, &si, 0); } VkPhysicalDeviceMemoryProperties memProps; vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps); return 0; }); threads.push_back(thread); } auto cpuTimeStart = System::cpuTime(); for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->start(); } for (uint32_t i = 0; i < kThreadCount; ++i) { threads[i]->wait(); delete threads[i]; } vk->vkQueueWaitIdle(mQueue); vk->vkDeviceWaitIdle(mDevice); auto cpuTime = System::cpuTime() - cpuTimeStart; uint64_t duration_us = cpuTime.wall_time_us; uint64_t duration_cpu_us = cpuTime.usageUs(); float ms = duration_us / 1000.0f; float sec = duration_us / 1000000.0f; float submitHz = (float)kTotalRecords / sec; printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2); vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs); vk->vkDestroyCommandPool(mDevice, commandPool, nullptr); EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets( mDevice, pool, kThreadCount, sets.data())); vk->vkDestroyDescriptorPool(mDevice, pool, nullptr); } INSTANTIATE_TEST_SUITE_P( MultipleTransports, VulkanHalTest, testing::ValuesIn(GoldfishOpenglTestEnv::getTransportsToTest())); } // namespace aemu