| // copyright (c) 2022 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 "VulkanTestHelper.h" |
| |
| #include "host-common/emugl_vm_operations.h" |
| #include "host-common/feature_control.h" |
| #include "host-common/logging.h" |
| #include "host-common/vm_operations.h" |
| |
| namespace gfxstream { |
| namespace vk { |
| namespace testing { |
| namespace { |
| |
| using ::android::base::BumpPool; |
| |
| bool validationErrorsFound = false; |
| |
| // Called back by the Vulkan validation layer in case of a validation error |
| VKAPI_ATTR VkBool32 VKAPI_CALL validationCallback( |
| VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, |
| const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { |
| if (severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { |
| ERR("Validation Layer: \"%s\"", pCallbackData->pMessage); |
| validationErrorsFound = true; |
| } |
| return VK_FALSE; |
| } |
| |
| gfxstream::host::FeatureSet getGfxstreamFeatures() { |
| gfxstream::host::FeatureSet features; |
| // Enable so that we can have VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
| features.GlDirectMem.enabled = true; |
| return features; |
| } |
| |
| } // namespace |
| |
| std::mutex VulkanTestHelper::mMutex; |
| |
| VulkanTestHelper::VulkanTestHelper() |
| : mLock(mMutex), |
| mVk(vkDispatch(/*forTesting=*/true)), |
| mLogger(), |
| mMetricsLogger(android::base::CreateMetricsLogger()), |
| mHealthMonitor(*mMetricsLogger), |
| mVkEmu(createGlobalVkEmulation(mVk, getGfxstreamFeatures())), |
| mBp(std::make_unique<BumpPool>()), |
| mDecoderContext(VkDecoderContext{.processName = "vulkan_test", |
| .gfxApiLogger = &mLogger, |
| .healthMonitor = &mHealthMonitor, |
| .metricsLogger = mMetricsLogger.get()}), |
| mTestDispatch(mVk, mBp.get(), &mDecoderContext) { |
| |
| // This is used by VkDecoderGlobalState::on_vkCreateInstance() |
| QAndroidVmOperations vmOps; |
| vmOps.setSkipSnapshotSave = [](bool) {}; |
| set_emugl_vm_operations(vmOps); |
| |
| validationErrorsFound = false; |
| } |
| |
| void VulkanTestHelper::destroy() { |
| if (mDevice) { |
| vk().vkDeviceWaitIdle(mDevice); |
| if (mCommandPool) vk().vkDestroyCommandPool(mDevice, mCommandPool, nullptr); |
| vk().vkDestroyDevice(mDevice, nullptr); |
| } |
| if (mInstance) { |
| if (mDebugMessenger) { |
| vk().vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr); |
| } |
| vk().vkDestroyInstance(mInstance, nullptr); |
| } |
| |
| mCommandPool = VK_NULL_HANDLE; |
| mDevice = VK_NULL_HANDLE; |
| mInstance = VK_NULL_HANDLE; |
| mDebugMessenger = VK_NULL_HANDLE; |
| |
| VkDecoderGlobalState::reset(); |
| teardownGlobalVkEmulation(); |
| } |
| |
| VulkanTestHelper::~VulkanTestHelper() { |
| destroy(); |
| if (mFailOnValidationErrors && validationErrorsFound) { |
| FATAL() << "Validation errors found. Aborting."; |
| } |
| } |
| |
| void VulkanTestHelper::initialize(const InitializationOptions& options) { |
| initVkEmulationFeatures(std::make_unique<VkEmulationFeatures>(VkEmulationFeatures{ |
| .astcLdrEmulationMode = options.astcLdrEmulationMode, |
| })); |
| |
| // Check that the validation layer is present |
| const char* validationLayer = "VK_LAYER_KHRONOS_validation"; |
| uint32_t layerCount; |
| vk().vkEnumerateInstanceLayerProperties(&layerCount, nullptr); |
| std::vector<VkLayerProperties> availableLayers(layerCount); |
| vk().vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); |
| |
| bool layerFound = false; |
| for (const auto& layerProperties : availableLayers) { |
| if (strcmp(validationLayer, layerProperties.layerName) == 0) { |
| layerFound = true; |
| break; |
| } |
| } |
| if (!layerFound) FATAL() << "Vulkan Validation Layer not found"; |
| |
| // Create the instance |
| VkApplicationInfo defaultAppInfo = { |
| .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, |
| .pApplicationName = "vulkan_test", |
| .pEngineName = "vulkan_test", |
| .apiVersion = VK_API_VERSION_1_1, |
| }; |
| |
| VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = { |
| .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, |
| .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | |
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | |
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, |
| .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | |
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | |
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, |
| .pfnUserCallback = validationCallback, |
| }; |
| |
| std::vector<const char*> extensions = {VK_EXT_DEBUG_UTILS_EXTENSION_NAME}; |
| for (const auto& extName : options.enabledExtensions) { |
| extensions.push_back(extName.c_str()); |
| } |
| |
| VkInstanceCreateInfo createInfo = { |
| .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
| .pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo, |
| .pApplicationInfo = options.appInfo ? &options.appInfo.value() : &defaultAppInfo, |
| .enabledLayerCount = 1, |
| .ppEnabledLayerNames = &validationLayer, |
| .enabledExtensionCount = static_cast<uint32_t>(extensions.size()), |
| .ppEnabledExtensionNames = extensions.data(), |
| }; |
| VK_CHECK(vk().vkCreateInstance(&createInfo, nullptr, &mInstance)); |
| |
| // Setup validation layer callbacks |
| VK_CHECK(vk().vkCreateDebugUtilsMessengerEXT(mInstance, &debugCreateInfo, nullptr, |
| &mDebugMessenger)); |
| |
| // Pick a physical device |
| uint32_t deviceCount = 0; |
| vk().vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr); |
| if (deviceCount == 0) FATAL() << "No Vulkan device found."; |
| std::vector<VkPhysicalDevice> devices(deviceCount); |
| VK_CHECK(vk().vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data())); |
| |
| mPhysicalDevice = devices[0]; |
| assert(mPhysicalDevice != VK_NULL_HANDLE); |
| |
| // Create the logical device |
| float queuePriority = 1.0f; |
| VkDeviceQueueCreateInfo queueCreateInfo = { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .queueFamilyIndex = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT), |
| .queueCount = 1, |
| .pQueuePriorities = &queuePriority, |
| }; |
| |
| VkDeviceCreateInfo deviceCreateInfo = { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
| .queueCreateInfoCount = 1, |
| .pQueueCreateInfos = &queueCreateInfo, |
| .enabledLayerCount = 1, |
| .ppEnabledLayerNames = &validationLayer, |
| .pEnabledFeatures = &options.deviceFeatures, |
| }; |
| VK_CHECK(vk().vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice)); |
| |
| // Get a graphics queue |
| vk().vkGetDeviceQueue(mDevice, queueCreateInfo.queueFamilyIndex, 0, &mGraphicsQueue); |
| assert(mGraphicsQueue != VK_NULL_HANDLE); |
| |
| // Create command pool |
| VkCommandPoolCreateInfo poolInfo{ |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
| .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, |
| .queueFamilyIndex = queueCreateInfo.queueFamilyIndex, |
| }; |
| VK_CHECK(vk().vkCreateCommandPool(mDevice, &poolInfo, nullptr, &mCommandPool)); |
| } |
| |
| bool VulkanTestHelper::hasValidationErrors() const { return validationErrorsFound; } |
| |
| VkCommandBuffer VulkanTestHelper::beginCommandBuffer() { |
| VkCommandBufferAllocateInfo allocInfo = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, |
| .commandPool = unbox_VkCommandPool(mCommandPool), |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, |
| .commandBufferCount = 1, |
| }; |
| VkCommandBuffer commandBuffer; |
| vk().vkAllocateCommandBuffers(mDevice, &allocInfo, &commandBuffer); |
| |
| VkCommandBufferBeginInfo beginInfo = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, |
| .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, |
| }; |
| vk().vkBeginCommandBuffer(commandBuffer, &beginInfo); |
| return commandBuffer; |
| } |
| |
| void VulkanTestHelper::submitCommandBuffer(VkCommandBuffer commandBuffer) { |
| vk().vkEndCommandBuffer(commandBuffer); |
| auto cmdBuf = unbox_VkCommandBuffer(commandBuffer); |
| |
| VkSubmitInfo submitInfo = { |
| .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| .commandBufferCount = 1, |
| .pCommandBuffers = &cmdBuf, |
| }; |
| vk().vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); |
| vk().vkQueueWaitIdle(mGraphicsQueue); |
| vk().vkFreeCommandBuffers(mDevice, mCommandPool, 1, &cmdBuf); |
| } |
| |
| uint32_t VulkanTestHelper::getQueueFamilyIndex(VkQueueFlagBits queueFlags) { |
| uint32_t queueFamilyCount = 0; |
| vk().vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyCount, nullptr); |
| |
| std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); |
| vk().vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyCount, |
| queueFamilies.data()); |
| for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilies.size()); i++) { |
| if (queueFamilies[i].queueFlags & queueFlags) { |
| return i; |
| } |
| } |
| |
| FATAL() << "No queue family found matching the requested flags"; |
| } |
| |
| uint32_t VulkanTestHelper::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { |
| VkPhysicalDeviceMemoryProperties memProperties; |
| vk().vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProperties); |
| |
| for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { |
| if ((typeFilter & (1 << i)) && |
| (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { |
| return i; |
| } |
| } |
| FATAL() << "failed to find suitable memory type!"; |
| } |
| |
| void VulkanTestHelper::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, |
| VkMemoryPropertyFlags properties, VkBuffer& buffer, |
| VkDeviceMemory& bufferMemory) { |
| VkBufferCreateInfo bufferInfo = { |
| .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
| .size = size, |
| .usage = usage, |
| .sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
| }; |
| VK_CHECK(vk().vkCreateBuffer(mDevice, &bufferInfo, nullptr, &buffer)); |
| |
| VkMemoryRequirements memRequirements; |
| vk().vkGetBufferMemoryRequirements(mDevice, buffer, &memRequirements); |
| |
| VkMemoryAllocateInfo allocInfo = { |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, |
| .allocationSize = memRequirements.size, |
| .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties), |
| }; |
| VK_CHECK(vk().vkAllocateMemory(mDevice, &allocInfo, nullptr, &bufferMemory)); |
| |
| vk().vkBindBufferMemory(mDevice, buffer, bufferMemory, 0); |
| } |
| |
| void VulkanTestHelper::transitionImageLayout(VkCommandBuffer cmdBuf, VkImage image, |
| VkImageLayout oldLayout, VkImageLayout newLayout) { |
| VkImageMemoryBarrier barrier = { |
| .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
| .oldLayout = oldLayout, |
| .newLayout = newLayout, |
| .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| .image = unbox_VkImage(image), |
| .subresourceRange = |
| { |
| .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
| .baseMipLevel = 0, |
| .levelCount = 1, |
| .baseArrayLayer = 0, |
| .layerCount = 1, |
| }, |
| }; |
| |
| switch (oldLayout) { |
| case VK_IMAGE_LAYOUT_UNDEFINED: |
| barrier.srcAccessMask = 0; |
| break; |
| case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: |
| barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; |
| break; |
| case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: |
| barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| break; |
| case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: |
| barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; |
| break; |
| default: |
| FATAL() << "Unsupported layout transition!"; |
| } |
| |
| switch (newLayout) { |
| case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: |
| barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| break; |
| case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: |
| barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; |
| break; |
| case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: |
| if (barrier.srcAccessMask == 0) { |
| barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; |
| } |
| barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; |
| break; |
| default: |
| FATAL() << "Unsupported layout transition!"; |
| } |
| vk().vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, |
| VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, |
| &barrier); |
| } |
| |
| } // namespace testing |
| } // namespace vk |
| } // namespace gfxstream |