blob: 6f9e3943fe841f7a2ea32b00a344ff90802291f9 [file] [log] [blame]
// Copyright 2020 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 "vulkan_context.h"
#include <assert.h>
#include <stddef.h>
#include <limits>
#include <memory>
#include <utility>
#include "src/graphics/tests/common/utils.h"
#include "vulkan/vulkan.hpp"
// This is a default implementation that returns no value. It's weak and can be overridden by
// config_query.cc or other implementations.
__attribute__((weak)) std::optional<uint32_t> GetGpuVendorId() { return {}; }
static inline uint32_t to_uint32(uint64_t val) {
assert(val <= std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(val);
}
vk::DebugUtilsMessengerCreateInfoEXT VulkanContext::default_debug_info_s_(
{} /* create flags */, vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation,
DebugUtilsTestCallback);
VulkanContext::ContextWithUserData VulkanContext::default_debug_callback_user_data_s_;
VulkanContext::VulkanContext(const vk::InstanceCreateInfo &instance_info,
std::optional<uint32_t> physical_device_index,
const vk::DeviceCreateInfo &device_info,
const vk::DeviceQueueCreateInfo &queue_info,
const vk::QueueFlags &queue_flags,
const vk::DebugUtilsMessengerCreateInfoEXT &debug_info,
ContextWithUserData debug_callback_user_data,
vk::Optional<const vk::AllocationCallbacks> allocator,
bool validation_layers_enabled, bool validation_errors_ignored)
: instance_info_(instance_info),
physical_device_index_(physical_device_index),
queue_family_index_(kInvalidQueueFamily),
queue_info_(queue_info),
device_info_(device_info),
debug_callback_user_data_(std::move(debug_callback_user_data)),
debug_info_(debug_info),
queue_flags_(queue_flags),
allocator_(allocator),
validation_layers_enabled_(validation_layers_enabled),
validation_errors_ignored_(validation_errors_ignored) {
assert(debug_info_.pUserData == nullptr &&
"Debug callback user data must be only set in |debug_callback_user_data|.");
}
VulkanContext::VulkanContext(std::optional<uint32_t> physical_device_index,
const vk::QueueFlags &queue_flags,
vk::Optional<const vk::AllocationCallbacks> allocator)
: physical_device_index_(physical_device_index),
queue_family_index_(kInvalidQueueFamily),
queue_info_(vk::DeviceQueueCreateFlags(), queue_family_index_, 1 /* queueCount */,
&queue_priority_),
device_info_(vk::DeviceCreateFlags(), 1 /* queueCreateInfoCount */, &queue_info_),
debug_info_(default_debug_info_s_),
queue_flags_(queue_flags),
allocator_(allocator) {}
bool VulkanContext::InitInstance() {
if (instance_initialized_) {
RTN_MSG(false, "Instance is already initialized.\n");
}
if (instance_info_.ppEnabledLayerNames && instance_info_.enabledLayerCount) {
// Tack custom layers on to |layers_|.
std::copy(instance_info_.ppEnabledLayerNames,
instance_info_.ppEnabledLayerNames + instance_info_.enabledLayerCount,
std::back_inserter(layers_));
}
if (instance_info_.ppEnabledExtensionNames && instance_info_.enabledExtensionCount) {
// Tack custom extensions on to |extensions_|.
std::copy(instance_info_.ppEnabledExtensionNames,
instance_info_.ppEnabledExtensionNames + instance_info_.enabledExtensionCount,
std::back_inserter(extensions_));
}
if (validation_layers_enabled_) {
debug_callback_user_data_.context_ = this;
debug_info_.pUserData = &debug_callback_user_data_;
layers_.emplace_back("VK_LAYER_KHRONOS_validation");
extensions_.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
// Install a debug utils messenger info struct by default if validation layers are enabled.
if (!DebugUtilsMessengerInstalled()) {
if (debug_info_.pNext) {
RTN_MSG(false, "Can't overwrite |debug_info_| structure chain.");
}
debug_info_.pNext = instance_info_.pNext;
instance_info_.pNext = &debug_info_;
}
} else {
if (DebugUtilsMessengerInstalled()) {
printf(
"VulkanContext::InitInstance: found debug utils messenger. "
"Expected |validation_layers_enabled| to be true.\n");
}
}
instance_info_.ppEnabledLayerNames = layers_.data();
instance_info_.enabledLayerCount = to_uint32(layers_.size());
instance_info_.ppEnabledExtensionNames = extensions_.data();
instance_info_.enabledExtensionCount = to_uint32(extensions_.size());
vk::ResultValue<vk::UniqueInstance> rv_instance(vk::Result::eNotReady, vk::UniqueInstance{});
if (allocator_) {
// Verify valid use of callbacks.
if (!(allocator_->pfnAllocation && allocator_->pfnReallocation && allocator_->pfnFree)) {
RTN_MSG(false, "Required allocator function is missing.\n");
}
if (allocator_->pfnInternalAllocation && !allocator_->pfnInternalFree) {
RTN_MSG(false, "pfnInternalAllocation defined without pfnInternalFree.\n");
}
if (allocator_->pfnInternalFree && !allocator_->pfnInternalAllocation) {
RTN_MSG(false, "pfnInternalFree defined without pfnInternalAllocation.\n");
}
rv_instance = vk::createInstanceUnique(instance_info_, allocator_);
} else {
rv_instance = vk::createInstanceUnique(instance_info_);
}
if (vk::Result::eSuccess != rv_instance.result) {
RTN_MSG(false, "VK Error - Create instance: %d (%s)\n", static_cast<int>(rv_instance.result),
vk::to_string(rv_instance.result).data());
}
instance_ = std::move(rv_instance.value);
if (validation_layers_enabled_) {
loader_.init(instance_.get(), vkGetInstanceProcAddr);
auto rv_messenger =
instance_->createDebugUtilsMessengerEXTUnique(debug_info_, nullptr, loader_);
if (rv_messenger.result != vk::Result::eSuccess) {
RTN_MSG(false, "VK Error - CreateDebugUtilsMessenger: %d (%s)\n",
static_cast<int>(rv_messenger.result), vk::to_string(rv_messenger.result).data());
}
messenger_ = std::move(rv_messenger.value);
}
instance_initialized_ = true;
return true;
}
bool VulkanContext::InitQueueFamily() {
if (!instance_initialized_) {
RTN_MSG(false, "Instance must be initialized before queue family.\n");
}
if (queue_family_initialized_) {
RTN_MSG(false, "Queue family is already initialized.\n");
}
auto [r_physical_devices, physical_devices] = instance_->enumeratePhysicalDevices();
if (vk::Result::eSuccess != r_physical_devices || physical_devices.empty()) {
RTN_MSG(false, "VK Error - No physical device found: %d (%s)\n",
static_cast<int>(r_physical_devices), vk::to_string(r_physical_devices).data());
}
if (!physical_device_index_) {
auto gpu_vendor_id = GetGpuVendorId();
if (gpu_vendor_id) {
for (size_t i = 0; i < physical_devices.size(); i++) {
auto properties = physical_devices[i].getProperties();
if (properties.vendorID == *gpu_vendor_id) {
physical_device_index_ = {i};
break;
}
}
if (!physical_device_index_) {
RTN_MSG(false, "Couldn't find GPU with vendor ID %d\n", *gpu_vendor_id);
}
} else {
physical_device_index_ = {0};
}
}
physical_device_ = physical_devices[*physical_device_index_];
const auto queue_families = physical_device_.getQueueFamilyProperties();
queue_family_index_ = to_uint32(queue_families.size());
for (size_t i = 0; i < queue_families.size(); ++i) {
if (queue_families[i].queueFlags & queue_flags_) {
queue_family_index_ = to_uint32(i);
break;
}
}
if (static_cast<size_t>(queue_family_index_) == queue_families.size()) {
queue_family_index_ = kInvalidQueueFamily;
RTN_MSG(false, "Couldn't find an appropriate queue family.\n");
}
queue_info_.queueFamilyIndex = queue_family_index_;
queue_family_initialized_ = true;
return true;
}
bool VulkanContext::InitDevice() {
if (!queue_family_initialized_) {
RTN_MSG(false, "Queue family must be initialized before device.\n");
}
if (device_initialized_) {
RTN_MSG(false, "Device is already initialized.\n");
}
vk::ResultValue<vk::UniqueDevice> rv_device(vk::Result::eNotReady, vk::UniqueDevice{});
if (allocator_) {
rv_device = physical_device_.createDeviceUnique(device_info_, allocator_);
} else {
rv_device = physical_device_.createDeviceUnique(device_info_);
}
if (vk::Result::eSuccess != rv_device.result) {
RTN_MSG(false, "VK Error - Create device: %d (%s)\n", static_cast<int>(rv_device.result),
vk::to_string(rv_device.result).data());
}
device_ = std::move(rv_device.value);
const auto &queue_create_info = device_info_.pQueueCreateInfos[0];
if (queue_create_info.flags) {
queue_ = device_->getQueue2(vk::DeviceQueueInfo2()
.setFlags(queue_create_info.flags)
.setQueueFamilyIndex(queue_family_index_));
} else {
queue_ = device_->getQueue(queue_family_index_, 0);
}
// Passing vkGetInstanceProcAddr means we can skip an unnecessary dlopen of the Vulkan loader,
// and also prevents a crash observed running in the Linux Termina VM.
loader_.init(instance_.get(), vkGetInstanceProcAddr, device_.get());
device_initialized_ = true;
return true;
}
bool VulkanContext::Init() {
if (initialized_ || instance_initialized_ || device_initialized_ || queue_family_initialized_) {
RTN_MSG(false, "Attempt to re-initialize VulkanContext.\n");
}
if (!InitInstance()) {
RTN_MSG(false, "Unable to initialize instance.\n");
}
if (!InitQueueFamily()) {
RTN_MSG(false, "Unable to initialize queue family.\n");
}
if (!InitDevice()) {
RTN_MSG(false, "Unable to initialize device.\n");
}
initialized_ = true;
return true;
}
bool VulkanContext::DebugUtilsMessengerInstalled() const {
bool installed = false;
VkBaseOutStructure *next = const_cast<VkBaseOutStructure *>(
reinterpret_cast<const VkBaseOutStructure *>(instance_info_.pNext));
while (next) {
const VkBaseOutStructure *base = reinterpret_cast<const VkBaseOutStructure *>(next);
if (base->sType == VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT) {
installed = true;
break;
}
next = next->pNext;
}
return installed;
}
bool VulkanContext::set_instance_info(const vk::InstanceCreateInfo &v) {
if (instance_initialized_) {
RTN_MSG(false, "set_instance_info ignored. Instance is already initialized.\n");
}
instance_info_ = v;
return true;
}
bool VulkanContext::set_device_info(const vk::DeviceCreateInfo &v) {
if (device_initialized_) {
RTN_MSG(false, "set_device_info ignored. Device is already initialized.\n");
}
device_info_ = v;
return true;
}
bool VulkanContext::set_queue_info(const vk::DeviceQueueCreateInfo &v) {
if (queue_family_initialized_) {
RTN_MSG(false, "set_queue_info ignored. Queue family is already initialized.\n");
}
queue_info_ = v;
return true;
}
bool VulkanContext::set_queue_flags(const vk::QueueFlags &v) {
if (queue_family_initialized_) {
RTN_MSG(false, "set_queue_flag_bits ignored. Queue family is already initialized.\n");
}
queue_flags_ = v;
return true;
}
void VulkanContext::set_debug_utils_messenger(
const vk::DebugUtilsMessengerCreateInfoEXT &debug_info, const ContextWithUserData &user_data) {
assert(debug_info.pUserData == nullptr &&
"User data must only be set in |user_data| as it will be overwritten.");
debug_info_ = debug_info;
debug_callback_user_data_ = user_data;
}
const vk::UniqueInstance &VulkanContext::instance() const {
if (!instance_initialized_) {
assert(false && "Instance is not initialized.\n");
}
return instance_;
}
const vk::PhysicalDevice &VulkanContext::physical_device() const {
if (!queue_family_initialized_) {
assert(false && "Queue family is not initialized.\n");
}
return physical_device_;
}
int VulkanContext::queue_family_index() const {
if (!queue_family_initialized_) {
assert(false && "Queue family is not initialized.\n");
}
return queue_family_index_;
}
const vk::UniqueDevice &VulkanContext::device() const {
if (!device_initialized_) {
assert(false && "Device is not initialized.\n");
}
return device_;
}
const vk::Queue &VulkanContext::queue() const {
if (!device_initialized_) {
assert(false && "Device is not initialized.\n");
}
return queue_;
}
VulkanContext::Builder::Builder()
: queue_priority_(0.0f),
queue_info_(vk::DeviceQueueCreateFlags(), 0, 1 /* queueCount */, &queue_priority_),
device_info_(vk::DeviceCreateFlags(), 1 /* queueCreateInfoCount */, &queue_info_),
queue_flags_(kDefaultQueueFlags),
allocator_(nullptr),
debug_info_(VulkanContext::default_debug_info_s_) {}
vk::DeviceCreateInfo VulkanContext::Builder::DeviceInfo() const { return device_info_; }
vk::DeviceQueueCreateInfo VulkanContext::Builder::QueueInfo() const { return queue_info_; }
VulkanContext::Builder &VulkanContext::Builder::set_allocator(
vk::Optional<const vk::AllocationCallbacks> v) {
allocator_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_instance_info(const vk::InstanceCreateInfo &v) {
instance_info_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_physical_device_index(const uint32_t v) {
physical_device_index_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_device_info(const vk::DeviceCreateInfo &v) {
device_info_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_queue_info(const vk::DeviceQueueCreateInfo &v) {
queue_info_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_queue_flags(const vk::QueueFlags &v) {
queue_flags_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_validation_layers_enabled(bool v) {
validation_layers_enabled_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_validation_errors_ignored(bool v) {
validation_errors_ignored_ = v;
return *this;
}
VulkanContext::Builder &VulkanContext::Builder::set_debug_utils_messenger(
const vk::DebugUtilsMessengerCreateInfoEXT &debug_info, const ContextWithUserData &user_data) {
assert(debug_info.pUserData == nullptr &&
"User data must only be set in |user_data| as it will be overwritten.");
debug_info_ = debug_info;
debug_callback_user_data_ = user_data;
return *this;
}
std::unique_ptr<VulkanContext> VulkanContext::Builder::Unique() const {
auto context = std::make_unique<VulkanContext>(
instance_info_, physical_device_index_, device_info_, queue_info_, queue_flags_, debug_info_,
debug_callback_user_data_, allocator_, validation_layers_enabled_,
validation_errors_ignored_);
if (!context->Init()) {
RTN_MSG(nullptr, "Failed to initialize VulkanContext.\n");
}
return context;
}