| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/lib/escher/vk/vulkan_device_queues.h" |
| |
| #include <set> |
| |
| #include "src/lib/fxl/logging.h" |
| #include "src/ui/lib/escher/impl/vulkan_utils.h" |
| |
| namespace escher { |
| |
| VulkanDeviceQueues::Caps::Caps(vk::PhysicalDevice device) { |
| { |
| vk::PhysicalDeviceProperties props = device.getProperties(); |
| max_image_width = props.limits.maxImageDimension2D; |
| max_image_height = props.limits.maxImageDimension2D; |
| device_api_version = props.apiVersion; |
| } |
| |
| { |
| auto formats = impl::GetSupportedDepthFormats(device, { |
| vk::Format::eD16Unorm, |
| vk::Format::eX8D24UnormPack32, |
| vk::Format::eD32Sfloat, |
| vk::Format::eS8Uint, |
| vk::Format::eD16UnormS8Uint, |
| vk::Format::eD24UnormS8Uint, |
| vk::Format::eD32SfloatS8Uint, |
| }); |
| depth_stencil_formats.insert(formats.begin(), formats.end()); |
| } |
| } |
| |
| vk::Format VulkanDeviceQueues::Caps::GetMatchingDepthStencilFormat( |
| std::vector<vk::Format> formats) const { |
| for (auto& fmt : formats) { |
| if (depth_stencil_formats.find(fmt) != depth_stencil_formats.end()) { |
| return fmt; |
| } |
| } |
| FXL_CHECK(false) << "no matching depth format found."; |
| return vk::Format::eUndefined; |
| } |
| |
| namespace { |
| |
| // Helper for PopulateProcAddrs(). |
| template <typename FuncT> |
| static FuncT GetDeviceProcAddr(vk::Device device, const char* func_name) { |
| FuncT func = reinterpret_cast<FuncT>(device.getProcAddr(func_name)); |
| FXL_CHECK(func) << "failed to find function address for: " << func_name; |
| return func; |
| } |
| |
| // Helper for VulkanDeviceQueues constructor. |
| VulkanDeviceQueues::ProcAddrs PopulateProcAddrs(vk::Device device, |
| const VulkanDeviceQueues::Params& params) { |
| #define GET_DEVICE_PROC_ADDR(XXX) result.XXX = GetDeviceProcAddr<PFN_vk##XXX>(device, "vk" #XXX) |
| |
| VulkanDeviceQueues::ProcAddrs result; |
| if (params.required_extension_names.find(VK_KHR_SWAPCHAIN_EXTENSION_NAME) != |
| params.required_extension_names.end() || |
| params.desired_extension_names.find(VK_KHR_SWAPCHAIN_EXTENSION_NAME) != |
| params.desired_extension_names.end()) { |
| GET_DEVICE_PROC_ADDR(CreateSwapchainKHR); |
| GET_DEVICE_PROC_ADDR(DestroySwapchainKHR); |
| GET_DEVICE_PROC_ADDR(GetSwapchainImagesKHR); |
| GET_DEVICE_PROC_ADDR(AcquireNextImageKHR); |
| GET_DEVICE_PROC_ADDR(QueuePresentKHR); |
| } |
| return result; |
| |
| #undef GET_DEVICE_PROC_ADDR |
| } |
| |
| // Return value for FindSuitablePhysicalDeviceAndQueueFamilies(). Valid if and |
| // only if device is non-null. Otherwise, no suitable device was found. |
| struct SuitablePhysicalDeviceAndQueueFamilies { |
| vk::PhysicalDevice physical_device; |
| uint32_t main_queue_family; |
| uint32_t transfer_queue_family; |
| }; |
| |
| SuitablePhysicalDeviceAndQueueFamilies FindSuitablePhysicalDeviceAndQueueFamilies( |
| const VulkanInstancePtr& instance, const VulkanDeviceQueues::Params& params) { |
| auto physical_devices = |
| ESCHER_CHECKED_VK_RESULT(instance->vk_instance().enumeratePhysicalDevices()); |
| |
| // A suitable main queue needs to support graphics and compute. |
| const auto kMainQueueFlags = vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute; |
| |
| // A specialized transfer queue will only support transfer; see comment below, |
| // where these flags are used. |
| const auto kTransferQueueFlags = |
| vk::QueueFlagBits::eTransfer | vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute; |
| |
| for (auto& physical_device : physical_devices) { |
| // Look for a physical device that has all required extensions. |
| if (!VulkanDeviceQueues::ValidateExtensions(physical_device, params.required_extension_names, |
| instance->params().layer_names)) { |
| continue; |
| } |
| |
| // Find the main queue family. If none is found, continue on to the next |
| // physical device. |
| auto queues = physical_device.getQueueFamilyProperties(); |
| const bool filter_queues_for_present = |
| params.surface && |
| !(params.flags & VulkanDeviceQueues::Params::kDisableQueueFilteringForPresent); |
| for (size_t i = 0; i < queues.size(); ++i) { |
| if (kMainQueueFlags == (queues[i].queueFlags & kMainQueueFlags)) { |
| if (filter_queues_for_present) { |
| // TODO: it is possible that there is no queue family that supports |
| // both graphics/compute and present. In this case, we would need a |
| // separate present queue. For now, just look for a single queue that |
| // meets all of our needs. |
| auto get_surface_support_result = |
| physical_device.getSurfaceSupportKHR(i, params.surface, instance->proc_addrs()); |
| FXL_CHECK(get_surface_support_result.result == vk::Result::eSuccess); |
| VkBool32 supports_present = get_surface_support_result.value; |
| if (supports_present != VK_TRUE) { |
| FXL_LOG(INFO) << "Queue supports graphics/compute, but not presentation"; |
| continue; |
| } |
| } |
| |
| // At this point, we have already succeeded. Now, try to find the |
| // optimal transfer queue family. |
| SuitablePhysicalDeviceAndQueueFamilies result; |
| result.physical_device = physical_device; |
| result.main_queue_family = i; |
| result.transfer_queue_family = i; |
| for (size_t j = 0; j < queues.size(); ++j) { |
| if ((queues[i].queueFlags & kTransferQueueFlags) == vk::QueueFlagBits::eTransfer) { |
| // We have found a transfer-only queue. This is the fastest way to |
| // upload data to the GPU. |
| result.transfer_queue_family = j; |
| break; |
| } |
| } |
| return result; |
| } |
| } |
| } |
| return {vk::PhysicalDevice(), 0, 0}; |
| } |
| |
| // Helper for ValidateExtensions(). |
| bool ValidateExtension(vk::PhysicalDevice device, const std::string name, |
| const std::vector<vk::ExtensionProperties>& base_extensions, |
| const std::set<std::string>& required_layer_names) { |
| auto found = std::find_if(base_extensions.begin(), base_extensions.end(), |
| [&name](const vk::ExtensionProperties& extension) { |
| return !strncmp(extension.extensionName, name.c_str(), |
| VK_MAX_EXTENSION_NAME_SIZE); |
| }); |
| if (found != base_extensions.end()) |
| return true; |
| |
| // Didn't find the extension in the base list of extensions. Perhaps it is |
| // implemented in a layer. |
| for (auto& layer_name : required_layer_names) { |
| auto layer_extensions = |
| ESCHER_CHECKED_VK_RESULT(device.enumerateDeviceExtensionProperties(layer_name)); |
| FXL_LOG(INFO) << "Looking for Vulkan device extension: " << name << " in layer: " << layer_name; |
| |
| auto found = std::find_if(layer_extensions.begin(), layer_extensions.end(), |
| [&name](vk::ExtensionProperties& extension) { |
| return !strncmp(extension.extensionName, name.c_str(), |
| VK_MAX_EXTENSION_NAME_SIZE); |
| }); |
| if (found != layer_extensions.end()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| fxl::RefPtr<VulkanDeviceQueues> VulkanDeviceQueues::New(VulkanInstancePtr instance, Params params) { |
| // Escher requires the memory_requirements_2 extension for the |
| // vma_gpu_allocator to function. |
| params.required_extension_names.insert(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); |
| |
| // If the params contain a surface, then ensure that the swapchain extension |
| // is supported so that we can render to that surface. |
| if (params.surface) { |
| params.required_extension_names.insert(VK_KHR_SWAPCHAIN_EXTENSION_NAME); |
| } |
| |
| #if defined(OS_FUCHSIA) |
| // If we're running on Fuchsia, make sure we have our semaphore extensions. |
| params.required_extension_names.insert(VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME); |
| params.required_extension_names.insert(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME); |
| #endif |
| |
| vk::PhysicalDevice physical_device; |
| uint32_t main_queue_family; |
| uint32_t transfer_queue_family; |
| { |
| SuitablePhysicalDeviceAndQueueFamilies result = |
| FindSuitablePhysicalDeviceAndQueueFamilies(instance, params); |
| FXL_CHECK(result.physical_device) << "Unable to find a suitable physical device."; |
| physical_device = result.physical_device; |
| main_queue_family = result.main_queue_family; |
| transfer_queue_family = result.transfer_queue_family; |
| } |
| |
| // Partially populate device capabilities from the physical device. |
| // Other stuff (e.g. which extensions are supported) will be added below. |
| VulkanDeviceQueues::Caps caps(physical_device); |
| |
| auto physical_device_memory_features = vk::PhysicalDeviceProtectedMemoryFeatures(); |
| auto physical_device_features = |
| vk::PhysicalDeviceFeatures2().setPNext(&physical_device_memory_features); |
| physical_device.getFeatures2(&physical_device_features); |
| |
| // Get the maximum supported Vulkan API version (minimum of device version and instance version). |
| uint32_t max_api_version = std::min(caps.device_api_version, instance->api_version()); |
| |
| // Protected memory is only supported with Vulkan API version 1.1. |
| if (!physical_device_memory_features.protectedMemory || max_api_version < VK_API_VERSION_1_1) { |
| FXL_LOG(INFO) << "Protected memory is not supported."; |
| caps.allow_protected_memory = false; |
| } else { |
| caps.allow_protected_memory = params.flags & VulkanDeviceQueues::Params::kAllowProtectedMemory; |
| } |
| |
| // Prepare to create the Device and Queues. |
| vk::DeviceQueueCreateInfo queue_info[2]; |
| const float kQueuePriority = 0; |
| queue_info[0] = vk::DeviceQueueCreateInfo(); |
| queue_info[0].queueFamilyIndex = main_queue_family; |
| queue_info[0].flags = caps.allow_protected_memory ? vk::DeviceQueueCreateFlagBits::eProtected |
| : vk::DeviceQueueCreateFlags(); |
| queue_info[0].queueCount = 1; |
| queue_info[0].pQueuePriorities = &kQueuePriority; |
| queue_info[1] = vk::DeviceQueueCreateInfo(); |
| queue_info[1].queueFamilyIndex = transfer_queue_family; |
| queue_info[1].queueCount = 1; |
| queue_info[1].pQueuePriorities = &kQueuePriority; |
| |
| // Prepare the list of extension names that will be used to create the device. |
| { |
| // These extensions were already validated by |
| // FindSuitablePhysicalDeviceAndQueueFamilies(); |
| caps.extensions = params.required_extension_names; |
| |
| // Request as many of the desired (but optional) extensions as possible. |
| auto extensions = |
| ESCHER_CHECKED_VK_RESULT(physical_device.enumerateDeviceExtensionProperties()); |
| |
| std::set<std::string> available_desired_extensions; |
| for (auto& name : params.desired_extension_names) { |
| if (ValidateExtension(physical_device, name, extensions, instance->params().layer_names)) { |
| caps.extensions.insert(name); |
| } |
| } |
| } |
| std::vector<const char*> extension_names; |
| for (auto& extension : caps.extensions) { |
| extension_names.push_back(extension.c_str()); |
| } |
| |
| // Specify the required physical device features, and verify that they are all |
| // supported. |
| // TODO(ES-111): instead of hard-coding the required features here, provide a |
| // mechanism for Escher clients to specify additional required features. |
| vk::PhysicalDeviceFeatures supported_device_features; |
| physical_device.getFeatures(&supported_device_features); |
| bool device_has_all_required_features = true; |
| |
| #define ADD_DESIRED_FEATURE(X) \ |
| if (supported_device_features.X) { \ |
| caps.enabled_features.X = true; \ |
| } else { \ |
| FXL_LOG(INFO) << "Desired Vulkan Device feature not supported: " << #X; \ |
| } |
| |
| #define ADD_REQUIRED_FEATURE(X) \ |
| caps.enabled_features.X = true; \ |
| if (!supported_device_features.X) { \ |
| FXL_LOG(ERROR) << "Required Vulkan Device feature not supported: " << #X; \ |
| device_has_all_required_features = false; \ |
| } |
| |
| // TODO(MA-478): We would like to make 'shaderClipDistance' a requirement on |
| // all Scenic platforms. For now, treat it as a DESIRED_FEATURE. |
| ADD_DESIRED_FEATURE(shaderClipDistance); |
| ADD_DESIRED_FEATURE(fillModeNonSolid); |
| |
| #undef ADD_DESIRED_FEATURE |
| #undef ADD_REQUIRED_FEATURE |
| |
| if (!device_has_all_required_features) { |
| return fxl::RefPtr<VulkanDeviceQueues>(); |
| } |
| |
| // Almost ready to create the device; start populating the VkDeviceCreateInfo. |
| vk::DeviceCreateInfo device_info; |
| device_info.queueCreateInfoCount = 2; |
| device_info.pQueueCreateInfos = queue_info; |
| device_info.enabledExtensionCount = extension_names.size(); |
| device_info.ppEnabledExtensionNames = extension_names.data(); |
| device_info.pEnabledFeatures = &caps.enabled_features; |
| if (caps.allow_protected_memory) { |
| device_info.pNext = &physical_device_memory_features; |
| } |
| |
| // It's possible that the main queue and transfer queue are in the same |
| // queue family. Adjust the device-creation parameters to account for this. |
| uint32_t main_queue_index = 0; |
| uint32_t transfer_queue_index = 0; |
| if (main_queue_family == transfer_queue_family) { |
| #if 0 |
| // TODO: it may be worthwhile to create multiple queues in the same family. |
| // However, we would need to look at VkQueueFamilyProperties.queueCount to |
| // make sure that we can create multiple queues for that family. For now, |
| // it is easier to share a single queue when the main/transfer queues are in |
| // the same family. |
| queue_info[0].queueCount = 2; |
| device_info.queueCreateInfoCount = 1; |
| transfer_queue_index = 1; |
| #else |
| device_info.queueCreateInfoCount = 1; |
| #endif |
| } |
| |
| // Create the device. |
| auto result = physical_device.createDevice(device_info); |
| if (result.result != vk::Result::eSuccess) { |
| FXL_LOG(WARNING) << "Could not create Vulkan Device: " << vk::to_string(result.result) << "."; |
| return fxl::RefPtr<VulkanDeviceQueues>(); |
| } |
| vk::Device device = result.value; |
| |
| // Obtain the queues that we requested to be created with the device. |
| vk::Queue main_queue; |
| if (caps.allow_protected_memory) { |
| auto main_queue_info2 = vk::DeviceQueueInfo2() |
| .setFlags(vk::DeviceQueueCreateFlagBits::eProtected) |
| .setQueueFamilyIndex(main_queue_family) |
| .setQueueIndex(main_queue_index); |
| main_queue = device.getQueue2(main_queue_info2); |
| } else { |
| main_queue = device.getQueue(main_queue_family, main_queue_index); |
| } |
| |
| vk::Queue transfer_queue; |
| if (main_queue_family == transfer_queue_family && main_queue_index == transfer_queue_index) { |
| transfer_queue = main_queue; |
| } else { |
| transfer_queue = device.getQueue(transfer_queue_family, transfer_queue_index); |
| } |
| |
| return fxl::AdoptRef(new VulkanDeviceQueues( |
| device, physical_device, main_queue, main_queue_family, transfer_queue, transfer_queue_family, |
| std::move(instance), std::move(params), std::move(caps))); |
| } |
| |
| VulkanDeviceQueues::VulkanDeviceQueues(vk::Device device, vk::PhysicalDevice physical_device, |
| vk::Queue main_queue, uint32_t main_queue_family, |
| vk::Queue transfer_queue, uint32_t transfer_queue_family, |
| VulkanInstancePtr instance, Params params, Caps caps) |
| : device_(device), |
| physical_device_(physical_device), |
| dispatch_loader_(instance->vk_instance(), device_), |
| main_queue_(main_queue), |
| main_queue_family_(main_queue_family), |
| transfer_queue_(transfer_queue), |
| transfer_queue_family_(transfer_queue_family), |
| instance_(std::move(instance)), |
| params_(std::move(params)), |
| caps_(std::move(caps)), |
| proc_addrs_(PopulateProcAddrs(device_, params_)) {} |
| |
| VulkanDeviceQueues::~VulkanDeviceQueues() { device_.destroy(); } |
| |
| bool VulkanDeviceQueues::ValidateExtensions(vk::PhysicalDevice device, |
| const std::set<std::string>& required_extension_names, |
| const std::set<std::string>& required_layer_names) { |
| auto extensions = ESCHER_CHECKED_VK_RESULT(device.enumerateDeviceExtensionProperties()); |
| |
| for (auto& name : required_extension_names) { |
| if (!ValidateExtension(device, name, extensions, required_layer_names)) { |
| FXL_LOG(WARNING) << "Vulkan has no device extension named: " << name; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| VulkanContext VulkanDeviceQueues::GetVulkanContext() const { |
| return escher::VulkanContext(instance_->vk_instance(), vk_physical_device(), vk_device(), |
| dispatch_loader(), vk_main_queue(), vk_main_queue_family(), |
| vk_transfer_queue(), vk_transfer_queue_family()); |
| } |
| |
| } // namespace escher |