// Copyright (c) 2016 The Chromium 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 "gpu/vulkan/vulkan_device_queue.h" #include #include #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gpu/config/gpu_info.h" // nogncheck #include "gpu/config/vulkan_info.h" #include "gpu/vulkan/vulkan_command_pool.h" #include "gpu/vulkan/vulkan_crash_keys.h" #include "gpu/vulkan/vulkan_fence_helper.h" #include "gpu/vulkan/vulkan_function_pointers.h" #include "gpu/vulkan/vulkan_util.h" #include "ui/gl/gl_angle_util_vulkan.h" namespace gpu { VulkanDeviceQueue::VulkanDeviceQueue(VkInstance vk_instance) : vk_instance_(vk_instance) {} VulkanDeviceQueue::VulkanDeviceQueue(VulkanInstance* instance) : vk_instance_(instance->vk_instance()), instance_(instance) {} VulkanDeviceQueue::~VulkanDeviceQueue() { DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_physical_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_queue_); } bool VulkanDeviceQueue::Initialize( uint32_t options, const GPUInfo* gpu_info, const std::vector& required_extensions, const std::vector& optional_extensions, bool allow_protected_memory, const GetPresentationSupportCallback& get_presentation_support, uint32_t heap_memory_limit) { DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_physical_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), owned_vk_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_queue_); if (VK_NULL_HANDLE == vk_instance_) return false; const VulkanInfo& info = instance_->vulkan_info(); VkResult result = VK_SUCCESS; VkQueueFlags queue_flags = 0; if (options & DeviceQueueOption::GRAPHICS_QUEUE_FLAG) queue_flags |= VK_QUEUE_GRAPHICS_BIT; // We prefer to use discrete GPU, integrated GPU is the second, and then // others. static constexpr int kDeviceTypeScores[] = { 0, // VK_PHYSICAL_DEVICE_TYPE_OTHER 3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU 4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU 2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU 1, // VK_PHYSICAL_DEVICE_TYPE_CPU }; static_assert(VK_PHYSICAL_DEVICE_TYPE_OTHER == 0, ""); static_assert(VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU == 1, ""); static_assert(VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU == 2, ""); static_assert(VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU == 3, ""); static_assert(VK_PHYSICAL_DEVICE_TYPE_CPU == 4, ""); int device_index = -1; int queue_index = -1; int device_score = -1; for (size_t i = 0; i < info.physical_devices.size(); ++i) { const auto& device_info = info.physical_devices[i]; const auto& device_properties = device_info.properties; if (device_properties.apiVersion < info.used_api_version) continue; // In dual-CPU cases, we cannot detect the active GPU correctly on Linux, // so don't select GPU device based on the |gpu_info|. #if !BUILDFLAG(IS_LINUX) // If gpu_info is provided, the device should match it. if (gpu_info && (device_properties.vendorID != gpu_info->gpu.vendor_id || device_properties.deviceID != gpu_info->gpu.device_id)) { continue; } #endif if (device_properties.deviceType < 0 || device_properties.deviceType > VK_PHYSICAL_DEVICE_TYPE_CPU) { DLOG(ERROR) << "Unsupported device type: " << device_properties.deviceType; continue; } const VkPhysicalDevice& device = device_info.device; bool found = false; for (size_t n = 0; n < device_info.queue_families.size(); ++n) { if ((device_info.queue_families[n].queueFlags & queue_flags) != queue_flags) { continue; } if (options & DeviceQueueOption::PRESENTATION_SUPPORT_QUEUE_FLAG && !get_presentation_support.Run(device, device_info.queue_families, n)) { continue; } if (kDeviceTypeScores[device_properties.deviceType] > device_score) { device_index = i; queue_index = static_cast(n); device_score = kDeviceTypeScores[device_properties.deviceType]; found = true; break; } } if (!found) continue; // Use the device, if it matches gpu_info. if (gpu_info) break; // If the device is a discrete GPU, we will use it. Otherwise go through // all the devices and find the device with the highest score. if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) break; } if (device_index == -1) { DLOG(ERROR) << "Cannot find capable device."; return false; } const auto& physical_device_info = info.physical_devices[device_index]; vk_physical_device_ = physical_device_info.device; vk_physical_device_properties_ = physical_device_info.properties; vk_physical_device_driver_properties_ = physical_device_info.driver_properties; vk_queue_index_ = queue_index; float queue_priority = 0.0f; VkDeviceQueueCreateInfo queue_create_info = {}; queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info.queueFamilyIndex = queue_index; queue_create_info.queueCount = 1; queue_create_info.pQueuePriorities = &queue_priority; queue_create_info.flags = allow_protected_memory ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0; std::vector enabled_extensions; for (const char* extension : required_extensions) { const auto it = std::find_if(physical_device_info.extensions.begin(), physical_device_info.extensions.end(), [extension](const VkExtensionProperties& p) { return std::strcmp(extension, p.extensionName) == 0; }); if (it == physical_device_info.extensions.end()) { // On Fuchsia, some device extensions are provided by layers. // TODO(penghuang): checking extensions against layer device extensions // too. #if !BUILDFLAG(IS_FUCHSIA) DLOG(ERROR) << "Required Vulkan extension " << extension << " is not supported."; return false; #endif } enabled_extensions.push_back(extension); } for (const char* extension : optional_extensions) { const auto it = std::find_if(physical_device_info.extensions.begin(), physical_device_info.extensions.end(), [extension](const VkExtensionProperties& p) { return std::strcmp(extension, p.extensionName) == 0; }); if (it == physical_device_info.extensions.end()) { DLOG(ERROR) << "Optional Vulkan extension " << extension << " is not supported."; } else { enabled_extensions.push_back(extension); } } crash_keys::vulkan_device_api_version.Set( VkVersionToString(vk_physical_device_properties_.apiVersion)); if (vk_physical_device_properties_.vendorID == 0x10DE) { // NVIDIA // 10 bits = major version (up to r1023) // 8 bits = minor version (up to 255) // 8 bits = secondary branch version/build version (up to 255) // 6 bits = tertiary branch/build version (up to 63) auto version = vk_physical_device_properties_.driverVersion; uint32_t major = (version >> 22) & 0x3ff; uint32_t minor = (version >> 14) & 0x0ff; uint32_t secondary_branch = (version >> 6) & 0x0ff; uint32_t tertiary_branch = version & 0x003f; crash_keys::vulkan_device_driver_version.Set(base::StringPrintf( "%d.%d.%d.%d", major, minor, secondary_branch, tertiary_branch)); } else { crash_keys::vulkan_device_driver_version.Set( VkVersionToString(vk_physical_device_properties_.driverVersion)); } crash_keys::vulkan_device_vendor_id.Set( base::StringPrintf("0x%04x", vk_physical_device_properties_.vendorID)); crash_keys::vulkan_device_id.Set( base::StringPrintf("0x%04x", vk_physical_device_properties_.deviceID)); static const char* kDeviceTypeNames[] = { "other", "integrated", "discrete", "virtual", "cpu", }; uint32_t gpu_type = vk_physical_device_properties_.deviceType; if (gpu_type >= std::size(kDeviceTypeNames)) gpu_type = 0; crash_keys::vulkan_device_type.Set(kDeviceTypeNames[gpu_type]); crash_keys::vulkan_device_name.Set(vk_physical_device_properties_.deviceName); // Disable all physical device features by default. enabled_device_features_2_ = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; // Android, Fuchsia, and Linux(VaapiVideoDecoder) need YCbCr sampler support. #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) if (!physical_device_info.feature_sampler_ycbcr_conversion) { LOG(ERROR) << "samplerYcbcrConversion is not supported."; return false; } sampler_ycbcr_conversion_features_ = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES}; sampler_ycbcr_conversion_features_.samplerYcbcrConversion = VK_TRUE; // Add VkPhysicalDeviceSamplerYcbcrConversionFeatures struct to pNext chain // of VkPhysicalDeviceFeatures2 to enable YCbCr sampler support. sampler_ycbcr_conversion_features_.pNext = enabled_device_features_2_.pNext; enabled_device_features_2_.pNext = &sampler_ycbcr_conversion_features_; #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) if (allow_protected_memory) { if (!physical_device_info.feature_protected_memory) { DLOG(ERROR) << "Protected memory is not supported"; return false; } protected_memory_features_ = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES}; protected_memory_features_.protectedMemory = VK_TRUE; // Add VkPhysicalDeviceProtectedMemoryFeatures struct to pNext chain // of VkPhysicalDeviceFeatures2 to enable YCbCr sampler support. protected_memory_features_.pNext = enabled_device_features_2_.pNext; enabled_device_features_2_.pNext = &protected_memory_features_; } VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; device_create_info.pNext = enabled_device_features_2_.pNext; device_create_info.queueCreateInfoCount = 1; device_create_info.pQueueCreateInfos = &queue_create_info; device_create_info.enabledExtensionCount = enabled_extensions.size(); device_create_info.ppEnabledExtensionNames = enabled_extensions.data(); device_create_info.pEnabledFeatures = &enabled_device_features_2_.features; result = vkCreateDevice(vk_physical_device_, &device_create_info, nullptr, &owned_vk_device_); if (VK_SUCCESS != result) { DLOG(ERROR) << "vkCreateDevice failed. result:" << result; return false; } enabled_extensions_ = gfx::ExtensionSet(std::begin(enabled_extensions), std::end(enabled_extensions)); if (!gpu::GetVulkanFunctionPointers()->BindDeviceFunctionPointers( owned_vk_device_, info.used_api_version, enabled_extensions_)) { vkDestroyDevice(owned_vk_device_, nullptr); owned_vk_device_ = VK_NULL_HANDLE; return false; } vk_device_ = owned_vk_device_; if (allow_protected_memory) { VkDeviceQueueInfo2 queue_info2 = {}; queue_info2.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2; queue_info2.flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT; queue_info2.queueFamilyIndex = queue_index; queue_info2.queueIndex = 0; vkGetDeviceQueue2(vk_device_, &queue_info2, &vk_queue_); } else { vkGetDeviceQueue(vk_device_, queue_index, 0, &vk_queue_); } std::vector heap_size_limit( VK_MAX_MEMORY_HEAPS, heap_memory_limit ? heap_memory_limit : VK_WHOLE_SIZE); vma::CreateAllocator(vk_physical_device_, vk_device_, vk_instance_, heap_size_limit.data(), &vma_allocator_); cleanup_helper_ = std::make_unique(this); allow_protected_memory_ = allow_protected_memory; return true; } bool VulkanDeviceQueue::InitCommon(VkPhysicalDevice vk_physical_device, VkDevice vk_device, VkQueue vk_queue, uint32_t vk_queue_index, gfx::ExtensionSet enabled_extensions) { DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_physical_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), owned_vk_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_device_); DCHECK_EQ(static_cast(VK_NULL_HANDLE), vk_queue_); vk_physical_device_ = vk_physical_device; vk_device_ = vk_device; vk_queue_ = vk_queue; vk_queue_index_ = vk_queue_index; enabled_extensions_ = std::move(enabled_extensions); vma::CreateAllocator(vk_physical_device_, vk_device_, vk_instance_, nullptr, &vma_allocator_); cleanup_helper_ = std::make_unique(this); return true; } bool VulkanDeviceQueue::InitializeFromANGLE() { const VulkanInfo& info = instance_->vulkan_info(); VkPhysicalDevice vk_physical_device = gl::QueryVkPhysicalDeviceFromANGLE(); if (vk_physical_device == VK_NULL_HANDLE) return false; int device_index = -1; for (size_t i = 0; i < info.physical_devices.size(); ++i) { if (info.physical_devices[i].device == vk_physical_device) { device_index = i; break; } } if (device_index == -1) { DLOG(ERROR) << "Cannot find physical device match ANGLE."; return false; } const auto& physical_device_info = info.physical_devices[device_index]; vk_physical_device_properties_ = physical_device_info.properties; vk_physical_device_driver_properties_ = physical_device_info.driver_properties; VkDevice vk_device = gl::QueryVkDeviceFromANGLE(); VkQueue vk_queue = gl::QueryVkQueueFromANGLE(); uint32_t vk_queue_index = gl::QueryVkQueueFramiliyIndexFromANGLE(); auto enabled_extensions = gl::QueryVkDeviceExtensionsFromANGLE(); if (!gpu::GetVulkanFunctionPointers()->BindDeviceFunctionPointers( vk_device, info.used_api_version, enabled_extensions)) { return false; } enabled_device_features_2_from_angle_ = gl::QueryVkEnabledDeviceFeaturesFromANGLE(); if (!enabled_device_features_2_from_angle_) return false; return InitCommon(vk_physical_device, vk_device, vk_queue, vk_queue_index, enabled_extensions); } bool VulkanDeviceQueue::InitializeForWebView( VkPhysicalDevice vk_physical_device, VkDevice vk_device, VkQueue vk_queue, uint32_t vk_queue_index, gfx::ExtensionSet enabled_extensions) { return InitCommon(vk_physical_device, vk_device, vk_queue, vk_queue_index, enabled_extensions); } bool VulkanDeviceQueue::InitializeForCompositorGpuThread( VkPhysicalDevice vk_physical_device, VkDevice vk_device, VkQueue vk_queue, uint32_t vk_queue_index, gfx::ExtensionSet enabled_extensions, const VkPhysicalDeviceFeatures2& vk_physical_device_features2) { // Currently VulkanDeviceQueue for drdc thread(aka CompositorGpuThread) uses // the same vulkan queue as the gpu main thread. Now since both gpu main and // drdc threads would be accessing/submitting work to the same queue, all the // queue access should be made thread safe. This is done by using locks. This // lock is per |vk_queue|. Note that we are intentionally overwriting a // previous lock if any. // Since the map itself would be accessed by multiple gpu threads, we need to // ensure that the access are thread safe. Here the locks are created and // written into the map only when drdc thread is initialized which happens // during GpuServiceImpl init. At this point none of the gpu threads would be // doing read access until GpuServiceImpl init completed. Hence its safe to // access map here. GetVulkanFunctionPointers()->per_queue_lock_map[vk_queue] = std::make_unique(); enabled_device_features_2_ = vk_physical_device_features2; return InitCommon(vk_physical_device, vk_device, vk_queue, vk_queue_index, enabled_extensions); } void VulkanDeviceQueue::Destroy() { if (cleanup_helper_) { cleanup_helper_->Destroy(); cleanup_helper_.reset(); } if (vma_allocator_ != VK_NULL_HANDLE) { vma::DestroyAllocator(vma_allocator_); vma_allocator_ = VK_NULL_HANDLE; } if (VK_NULL_HANDLE != owned_vk_device_) { vkDestroyDevice(owned_vk_device_, nullptr); owned_vk_device_ = VK_NULL_HANDLE; // Clear all the entries from this map since the device and hence all the // generated queue(and their corresponding lock) from this device is // destroyed. // This happens when VulkanDeviceQueue is destroyed on gpu main thread // during GpuServiceImpl destruction which happens after CompositorGpuThread // is destroyed. Hence CompositorGpuThread would not be accessing the map at // this point and its thread safe to delete map entries here. GetVulkanFunctionPointers()->per_queue_lock_map.clear(); } vk_device_ = VK_NULL_HANDLE; vk_queue_ = VK_NULL_HANDLE; vk_queue_index_ = 0; vk_physical_device_ = VK_NULL_HANDLE; } std::unique_ptr VulkanDeviceQueue::CreateCommandPool() { std::unique_ptr command_pool(new VulkanCommandPool(this)); if (!command_pool->Initialize()) return nullptr; return command_pool; } } // namespace gpu