#include "gpu/vulkan/vulkan_device_queue.h"
#include <algorithm>
#include <array>
#include <bit>
#include <cstring>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "gpu/config/gpu_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_info.h"
#include "gpu/vulkan/vulkan_util.h"
#include "ui/gl/gl_angle_util_vulkan.h"
namespace gpu {
namespace {
VkDeviceSize GetPreferredVMALargeHeapBlockSize() {
constexpr VkDeviceSize kVulkanVMALargeHeapBlockSize = 64 * 1024;
return kVulkanVMALargeHeapBlockSize;
}
#if BUILDFLAG(IS_ANDROID)
class VulkanMetric final
: public base::android::PreFreezeBackgroundMemoryTrimmer::PreFreezeMetric {
public:
explicit VulkanMetric(VmaAllocator vma_allocator)
: PreFreezeMetric("Vulkan"), vma_allocator_(vma_allocator) {
base::android::PreFreezeBackgroundMemoryTrimmer::RegisterMemoryMetric(this);
}
~VulkanMetric() override {
base::android::PreFreezeBackgroundMemoryTrimmer::UnregisterMemoryMetric(
this);
}
private:
std::optional<base::ByteCount> Measure() const override {
auto allocated_used = vma::GetTotalAllocatedAndUsedMemory(vma_allocator_);
return base::ByteCount::FromUnsigned(allocated_used.first);
}
VmaAllocator vma_allocator_;
};
#endif
}
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<VkPhysicalDevice>(VK_NULL_HANDLE), vk_physical_device_);
DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), vk_device_);
DCHECK_EQ(static_cast<VkQueue>(VK_NULL_HANDLE), vk_queue_);
}
bool VulkanDeviceQueue::Initialize(
uint32_t options,
const GPUInfo* gpu_info,
const std::vector<const char*>& required_extensions,
const std::vector<const char*>& optional_extensions,
bool allow_protected_memory,
const GetPresentationSupportCallback& get_presentation_support,
uint32_t heap_memory_limit,
const bool is_thread_safe) {
DCHECK_EQ(static_cast<VkPhysicalDevice>(VK_NULL_HANDLE), vk_physical_device_);
DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), owned_vk_device_);
DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), vk_device_);
DCHECK_EQ(static_cast<VkQueue>(VK_NULL_HANDLE), vk_queue_);
DCHECK_EQ(static_cast<VmaAllocator>(VK_NULL_HANDLE), owned_vma_allocator_);
DCHECK_EQ(static_cast<VmaAllocator>(VK_NULL_HANDLE), vma_allocator_);
DCHECK_EQ(nullptr, angle_display_);
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;
}
if (allow_protected_memory) {
queue_flags |= VK_QUEUE_PROTECTED_BIT;
}
static constexpr auto kDeviceTypeScores = std::to_array<int>({
0,
3,
4,
2,
1,
});
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;
#if !BUILDFLAG(IS_LINUX)
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<int>(n);
device_score = kDeviceTypeScores[device_properties.deviceType];
found = true;
break;
}
}
if (!found)
continue;
if (gpu_info)
break;
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;
drm_device_id_ = physical_device_info.drm_device_id;
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<const char*> enabled_extensions;
for (const char* extension : required_extensions) {
if (std::ranges::none_of(physical_device_info.extensions,
[extension](const VkExtensionProperties& p) {
return UNSAFE_TODO(std::strcmp(
extension, p.extensionName)) == 0;
})) {
#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) {
if (std::ranges::none_of(physical_device_info.extensions,
[extension](const VkExtensionProperties& p) {
return UNSAFE_TODO(std::strcmp(
extension, p.extensionName)) == 0;
})) {
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) {
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 auto kDeviceTypeNames = std::to_array<const char*>({
"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);
enabled_device_features_2_ = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ARKWEB)
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;
sampler_ycbcr_conversion_features_.pNext = enabled_device_features_2_.pNext;
enabled_device_features_2_.pNext = &sampler_ycbcr_conversion_features_;
#endif
if (allow_protected_memory) {
if (!physical_device_info.feature_protected_memory) {
LOG(DFATAL)
<< "Protected memory is not supported. Vulkan is unavailable.";
return false;
}
protected_memory_features_ = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES};
protected_memory_features_.protectedMemory = VK_TRUE;
protected_memory_features_.pNext = enabled_device_features_2_.pNext;
enabled_device_features_2_.pNext = &protected_memory_features_;
}
instance_->skia_features().addFeaturesToQuery(
physical_device_info.extensions.data(),
physical_device_info.extensions.size(), enabled_device_features_2_);
vkGetPhysicalDeviceFeatures2(vk_physical_device_,
&enabled_device_features_2_);
instance_->skia_features().addFeaturesToEnable(enabled_extensions,
enabled_device_features_2_);
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<VkDeviceSize> 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_,
enabled_extensions_, GetPreferredVMALargeHeapBlockSize(),
heap_size_limit.data(), is_thread_safe,
&owned_vma_allocator_);
vma_allocator_ = owned_vma_allocator_;
skia_vk_memory_allocator_ =
sk_make_sp<gpu::SkiaVulkanMemoryAllocator>(vma_allocator_);
cleanup_helper_ = std::make_unique<VulkanFenceHelper>(this);
allow_protected_memory_ = allow_protected_memory;
#if BUILDFLAG(IS_ANDROID)
if (!metric_) {
metric_ = std::make_unique<VulkanMetric>(vma_allocator());
}
#endif
if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "vulkan", base::SingleThreadTaskRunner::GetCurrentDefault());
}
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<VkPhysicalDevice>(VK_NULL_HANDLE), vk_physical_device_);
DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), owned_vk_device_);
DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), vk_device_);
DCHECK_EQ(static_cast<VkQueue>(VK_NULL_HANDLE), vk_queue_);
DCHECK_EQ(static_cast<VmaAllocator>(VK_NULL_HANDLE), owned_vma_allocator_);
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);
if (vma_allocator_ == VK_NULL_HANDLE) {
vma::CreateAllocator(vk_physical_device_, vk_device_, vk_instance_,
enabled_extensions_,
GetPreferredVMALargeHeapBlockSize(),
nullptr,
false, &owned_vma_allocator_);
vma_allocator_ = owned_vma_allocator_;
#if BUILDFLAG(IS_ANDROID)
if (!metric_) {
metric_ = std::make_unique<VulkanMetric>(vma_allocator());
}
#endif
}
skia_vk_memory_allocator_ =
sk_make_sp<gpu::SkiaVulkanMemoryAllocator>(vma_allocator_);
cleanup_helper_ = std::make_unique<VulkanFenceHelper>(this);
if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "vulkan", base::SingleThreadTaskRunner::GetCurrentDefault());
}
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;
angle_display_ = gl::QueryDisplayFromANGLE();
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,
void* vk_queue_lock_context,
uint32_t vk_queue_index,
gfx::ExtensionSet enabled_extensions,
const VkPhysicalDeviceFeatures2& vk_physical_device_features2,
VmaAllocator vma_allocator) {
GetVulkanFunctionPointers()->per_queue_lock_map[vk_queue] =
std::make_unique<gpu::VulkanQueueLock>(vk_queue_lock_context);
enabled_device_features_2_ = vk_physical_device_features2;
vma_allocator_ = vma_allocator;
return InitCommon(vk_physical_device, vk_device, vk_queue, vk_queue_index,
enabled_extensions);
}
void VulkanDeviceQueue::Destroy() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
#if BUILDFLAG(IS_ANDROID)
metric_ = nullptr;
#endif
if (cleanup_helper_) {
cleanup_helper_->Destroy();
cleanup_helper_.reset();
}
if (owned_vma_allocator_ != VK_NULL_HANDLE) {
vma::DestroyAllocator(owned_vma_allocator_);
owned_vma_allocator_ = VK_NULL_HANDLE;
}
if (owned_vk_device_ != VK_NULL_HANDLE) {
vkDestroyDevice(owned_vk_device_, nullptr);
owned_vk_device_ = VK_NULL_HANDLE;
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;
vma_allocator_ = VK_NULL_HANDLE;
}
std::unique_ptr<VulkanCommandPool> VulkanDeviceQueue::CreateCommandPool() {
std::unique_ptr<VulkanCommandPool> command_pool(new VulkanCommandPool(this));
if (!command_pool->Initialize())
return nullptr;
return command_pool;
}
bool VulkanDeviceQueue::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
std::string path =
base::StringPrintf("gpu/vulkan/vma_allocator_%p", vma_allocator());
if (pmd->GetAllocatorDump(path)) {
return true;
}
auto* dump = pmd->CreateAllocatorDump(path);
auto allocated_used = vma::GetTotalAllocatedAndUsedMemory(vma_allocator());
uint32_t lazy_allocated_size =
skia_vk_memory_allocator_->totalLazyAllocatedMemory();
dump->AddScalar("allocated_size", "bytes",
allocated_used.first - lazy_allocated_size);
dump->AddScalar("used_size", "bytes",
allocated_used.second - lazy_allocated_size);
dump->AddScalar("fragmentation_size", "bytes",
allocated_used.first - allocated_used.second);
dump->AddScalar("lazy_allocated_size", "bytes", lazy_allocated_size);
dump->AddScalar("lazy_used_size", "bytes", lazy_allocated_size);
return true;
}
}