mirror of
https://github.com/qdrant/qdrant.git
synced 2026-07-29 14:11:32 -05:00
* Delete unused code * restore initialize_global * drop BadShardSelection * Remove now obsolete allow(dead_code) attributes * Remove more dead code --------- Co-authored-by: timvisee <tim@visee.me>
417 lines
15 KiB
Rust
417 lines
15 KiB
Rust
use std::collections::HashMap;
|
|
use std::ffi::{CString, c_char};
|
|
use std::ops::Deref;
|
|
use std::sync::Arc;
|
|
|
|
use ash::vk;
|
|
use parking_lot::Mutex;
|
|
|
|
use crate::*;
|
|
|
|
static APPLICATION_NAME: &std::ffi::CStr = c"qdrant";
|
|
|
|
/// `Instance` is a Vulkan instance wrapper.
|
|
/// It's a root structure for all Vulkan operations and provides access the API.
|
|
/// It also manages all Vulkan API layers and API extensions.
|
|
pub struct Instance {
|
|
/// Vulkan API entry point. It should be kept alive while the instance is alive.
|
|
_entry: ash::Entry,
|
|
|
|
/// Native Vulkan instance handle.
|
|
vk_instance: ash::Instance,
|
|
|
|
/// List of physical devices found by the Vulkan instance.
|
|
vk_physical_devices: Vec<PhysicalDevice>,
|
|
|
|
/// CPU allocator.
|
|
allocation_callbacks: Option<Box<dyn AllocationCallbacks>>,
|
|
|
|
/// Validation layer handler.
|
|
vk_debug_utils_loader: Option<ash::ext::debug_utils::Instance>,
|
|
|
|
/// Validation layer messenger.
|
|
vk_debug_messenger: vk::DebugUtilsMessengerEXT,
|
|
|
|
/// Shader compiler.
|
|
compiler: Mutex<shaderc::Compiler>,
|
|
|
|
/// Debug messenger for the instance. It contains validation error callbacks.
|
|
/// Should be kept alive while the instance is alive because it contains raw pointers to callbacks.
|
|
_debug_messenger: Option<Box<dyn DebugMessenger>>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
/// Hardware type of the physical device.
|
|
pub enum PhysicalDeviceType {
|
|
/// Discrete GPU like Nvidia or AMD.
|
|
Discrete,
|
|
/// Integrated graphics like Intel HD Graphics.
|
|
Integrated,
|
|
/// Other types of hardware like software emulated GPU.
|
|
Other,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct PhysicalDevice {
|
|
pub vk_physical_device: vk::PhysicalDevice,
|
|
pub name: String,
|
|
pub device_type: PhysicalDeviceType,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct InstanceBuilder {
|
|
debug_messenger: Option<Box<dyn DebugMessenger>>,
|
|
allocation_callbacks: Option<Box<dyn AllocationCallbacks>>,
|
|
dump_api: bool,
|
|
}
|
|
|
|
impl InstanceBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Set debug messenger for the instance.
|
|
pub fn with_debug_messenger(mut self, debug_messenger: Box<dyn DebugMessenger>) -> Self {
|
|
self.debug_messenger = Some(debug_messenger);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> GpuResult<Arc<Instance>> {
|
|
Instance::new(
|
|
self.debug_messenger,
|
|
self.allocation_callbacks,
|
|
self.dump_api,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Instance {
|
|
pub fn builder() -> InstanceBuilder {
|
|
InstanceBuilder::new()
|
|
}
|
|
|
|
fn new(
|
|
debug_messenger: Option<Box<dyn DebugMessenger>>,
|
|
allocation_callbacks: Option<Box<dyn AllocationCallbacks>>,
|
|
dump_api: bool,
|
|
) -> GpuResult<Arc<Self>> {
|
|
// Create a shader compiler before we start.
|
|
// It's used to compile GLSL into SPIR-V.
|
|
let compiler = Mutex::new(
|
|
shaderc::Compiler::new()
|
|
.map_err(|_| GpuError::Other("Failed to create shaderc compiler".to_string()))?,
|
|
);
|
|
|
|
// Create Vulkan API entry point.
|
|
let entry = unsafe {
|
|
ash::Entry::load().map_err(|e| {
|
|
GpuError::Other(format!("Failed to create Vulkan API entry point {e:?}"))
|
|
})?
|
|
};
|
|
|
|
// Collect Vulkan application info.
|
|
// It contains application name and required Vulkan API version.
|
|
let app_info = vk::ApplicationInfo::default()
|
|
.application_name(APPLICATION_NAME)
|
|
.application_version(0)
|
|
.engine_name(APPLICATION_NAME)
|
|
.engine_version(0)
|
|
.api_version(vk::make_api_version(0, 1, 3, 0));
|
|
|
|
// Collect Vulkan API extensions and convert it in raw pointers.
|
|
let extensions = Self::extensions_list(debug_messenger.is_some());
|
|
// Check presence of all required extensions.
|
|
Self::check_extensions_list(&entry, &extensions)?;
|
|
let extensions_cstr: Vec<CString> = extensions
|
|
.iter()
|
|
.filter_map(|s| CString::new(s.clone().into_bytes()).ok())
|
|
.collect();
|
|
let extension_names_raw: Vec<*const c_char> = extensions_cstr
|
|
.iter()
|
|
.map(|raw_name| raw_name.as_ptr())
|
|
.collect();
|
|
|
|
// Collect Vulkan API layers and convert it in raw pointers.
|
|
let layers = Self::layers_list(debug_messenger.is_some(), dump_api);
|
|
// Check presence of all required layers.
|
|
Self::check_layers_list(&entry, &layers)?;
|
|
let layers_cstr: Vec<CString> = layers
|
|
.iter()
|
|
.filter_map(|s| CString::new(s.clone().into_bytes()).ok())
|
|
.collect();
|
|
let layers_raw: Vec<*const c_char> = layers_cstr
|
|
.iter()
|
|
.map(|raw_name| raw_name.as_ptr())
|
|
.collect();
|
|
|
|
// If we provide debug messenger, we need to create a debug messenger info.
|
|
let mut debug_utils_create_info = debug_messenger
|
|
.as_deref()
|
|
.map(Self::debug_messenger_create_info);
|
|
|
|
let create_flags = if cfg!(any(target_os = "macos")) {
|
|
// On MacOS we need to enable portability extension to enable MoltenVK.
|
|
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
|
|
} else {
|
|
vk::InstanceCreateFlags::default()
|
|
};
|
|
|
|
// Collect all parameters together and create Vulkan instance.
|
|
let mut create_info = vk::InstanceCreateInfo::default()
|
|
.flags(create_flags)
|
|
.application_info(&app_info)
|
|
.enabled_layer_names(&layers_raw)
|
|
.enabled_extension_names(&extension_names_raw);
|
|
|
|
if let Some(debug_utils_create_info) = &mut debug_utils_create_info {
|
|
create_info = create_info.push_next(debug_utils_create_info);
|
|
}
|
|
|
|
// Get CPU allocation callbacks if they are provided.
|
|
let vk_allocation_callbacks = allocation_callbacks
|
|
.as_ref()
|
|
.map(|a| a.allocation_callbacks());
|
|
|
|
// Finally, create Vulkan instance.
|
|
let vk_instance: ash::Instance =
|
|
unsafe { entry.create_instance(&create_info, vk_allocation_callbacks)? };
|
|
|
|
// Find all available physical GPU devices.
|
|
let vk_physical_devices_result = unsafe { vk_instance.enumerate_physical_devices() };
|
|
let vk_physical_devices = match vk_physical_devices_result {
|
|
Ok(vk_physical_devices) => vk_physical_devices,
|
|
Err(e) => {
|
|
// Don't forget to destroy the instance if we failed to find any physical devices.
|
|
unsafe {
|
|
vk_instance.destroy_instance(vk_allocation_callbacks);
|
|
}
|
|
return Err(GpuError::from(e));
|
|
}
|
|
};
|
|
|
|
let vk_physical_devices = vk_physical_devices
|
|
.iter()
|
|
.map(|&vk_physical_device| {
|
|
let device_properties =
|
|
unsafe { vk_instance.get_physical_device_properties(vk_physical_device) };
|
|
let device_name =
|
|
unsafe { ::std::ffi::CStr::from_ptr(device_properties.device_name.as_ptr()) };
|
|
let device_name = device_name.to_str().unwrap_or("Unnamed GPU").to_owned();
|
|
|
|
let device_type = match device_properties.device_type {
|
|
vk::PhysicalDeviceType::DISCRETE_GPU => PhysicalDeviceType::Discrete,
|
|
vk::PhysicalDeviceType::INTEGRATED_GPU => PhysicalDeviceType::Integrated,
|
|
_ => PhysicalDeviceType::Other,
|
|
};
|
|
|
|
log::info!("Found GPU device: {device_name}");
|
|
PhysicalDevice {
|
|
vk_physical_device,
|
|
name: device_name,
|
|
device_type,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if vk_physical_devices.is_empty() {
|
|
// Don't forget to destroy the instance if we failed to find any physical devices.
|
|
unsafe {
|
|
vk_instance.destroy_instance(vk_allocation_callbacks);
|
|
}
|
|
return Err(GpuError::Other(
|
|
"No Vulkan physical devices found".to_string(),
|
|
));
|
|
}
|
|
|
|
// If we have a debug messenger, we need to create it.
|
|
let (vk_debug_utils_loader, vk_debug_messenger) = if let Some(debug_messenger) =
|
|
debug_messenger.as_ref()
|
|
{
|
|
let debug_utils_loader = ash::ext::debug_utils::Instance::new(&entry, &vk_instance);
|
|
let messenger_create_info = Self::debug_messenger_create_info(debug_messenger.deref());
|
|
let utils_messenger_result = unsafe {
|
|
debug_utils_loader
|
|
.create_debug_utils_messenger(&messenger_create_info, vk_allocation_callbacks)
|
|
};
|
|
|
|
let utils_messenger = match utils_messenger_result {
|
|
Ok(messenger) => messenger,
|
|
Err(e) => {
|
|
// Don't forget to destroy the instance if we failed to create a debug messenger.
|
|
unsafe {
|
|
vk_instance.destroy_instance(vk_allocation_callbacks);
|
|
}
|
|
return Err(GpuError::from(e));
|
|
}
|
|
};
|
|
(Some(debug_utils_loader), utils_messenger)
|
|
} else {
|
|
(None, vk::DebugUtilsMessengerEXT::null())
|
|
};
|
|
|
|
Ok(Arc::new(Self {
|
|
_entry: entry,
|
|
vk_instance,
|
|
vk_physical_devices,
|
|
allocation_callbacks,
|
|
vk_debug_utils_loader,
|
|
vk_debug_messenger,
|
|
compiler,
|
|
_debug_messenger: debug_messenger,
|
|
}))
|
|
}
|
|
|
|
fn debug_messenger_create_info(
|
|
debug_messenger: &dyn DebugMessenger,
|
|
) -> vk::DebugUtilsMessengerCreateInfoEXT<'_> {
|
|
vk::DebugUtilsMessengerCreateInfoEXT::default()
|
|
.flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty())
|
|
.message_severity(debug_messenger.severity_flags())
|
|
.message_type(debug_messenger.message_type_flags())
|
|
.pfn_user_callback(debug_messenger.callback())
|
|
}
|
|
|
|
pub fn cpu_allocation_callbacks(&self) -> Option<&vk::AllocationCallbacks<'_>> {
|
|
self.allocation_callbacks
|
|
.as_ref()
|
|
.map(|alloc| alloc.allocation_callbacks())
|
|
}
|
|
|
|
pub fn vk_instance(&self) -> &ash::Instance {
|
|
&self.vk_instance
|
|
}
|
|
|
|
pub fn physical_devices(&self) -> &[PhysicalDevice] {
|
|
&self.vk_physical_devices
|
|
}
|
|
|
|
pub fn compile_shader(
|
|
&self,
|
|
shader: &str,
|
|
shader_name: &str,
|
|
defines: Option<&HashMap<String, Option<String>>>,
|
|
includes: Option<&HashMap<String, String>>,
|
|
) -> GpuResult<Vec<u8>> {
|
|
let mut options = shaderc::CompileOptions::new()
|
|
.map_err(|_| GpuError::Other("Failed to create shaderc compile options".to_string()))?;
|
|
options.set_optimization_level(shaderc::OptimizationLevel::Performance);
|
|
options.set_target_env(
|
|
shaderc::TargetEnv::Vulkan,
|
|
shaderc::EnvVersion::Vulkan1_3 as u32,
|
|
);
|
|
options.set_target_spirv(shaderc::SpirvVersion::V1_3);
|
|
|
|
if let Some(defines) = defines {
|
|
for (define, value) in defines {
|
|
match value {
|
|
Some(value) => {
|
|
options.add_macro_definition(define, Some(value));
|
|
}
|
|
None => {
|
|
options.add_macro_definition(define, None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(includes) = includes {
|
|
options.set_include_callback(|filename, _, _, _| {
|
|
if let Some(code) = includes.get(filename) {
|
|
Ok(shaderc::ResolvedInclude {
|
|
resolved_name: filename.to_string(),
|
|
content: code.to_owned(),
|
|
})
|
|
} else {
|
|
Err(format!("Include file not found: {filename}"))
|
|
}
|
|
});
|
|
}
|
|
|
|
let compiler = self.compiler.lock();
|
|
let result = compiler
|
|
.compile_into_spirv(
|
|
shader,
|
|
shaderc::ShaderKind::Compute,
|
|
shader_name,
|
|
"main",
|
|
Some(&options),
|
|
)
|
|
.map_err(|e| GpuError::Other(format!("Failed to compile shader: {e:?}")))?;
|
|
Ok(result.as_binary_u8().to_owned())
|
|
}
|
|
|
|
fn layers_list(validation: bool, dump_api: bool) -> Vec<String> {
|
|
let mut result = Vec::new();
|
|
if validation {
|
|
result.push("VK_LAYER_KHRONOS_validation".to_owned());
|
|
}
|
|
if dump_api {
|
|
result.push("VK_LAYER_LUNARG_api_dump".to_owned());
|
|
}
|
|
result
|
|
}
|
|
|
|
fn extensions_list(validation: bool) -> Vec<String> {
|
|
let mut extensions_list = Vec::new();
|
|
if validation && let Ok(ext) = ash::ext::debug_utils::NAME.to_str() {
|
|
extensions_list.push(ext.to_string());
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
if let Ok(ext) = ash::khr::portability_enumeration::NAME.to_str() {
|
|
extensions_list.push(ext.to_string());
|
|
}
|
|
if let Ok(ext) = ash::khr::get_physical_device_properties2::NAME.to_str() {
|
|
extensions_list.push(ext.to_string());
|
|
}
|
|
}
|
|
extensions_list
|
|
}
|
|
|
|
fn check_extensions_list(entry: &ash::Entry, extensions: &[String]) -> GpuResult<()> {
|
|
let extension_properties = unsafe { entry.enumerate_instance_extension_properties(None)? };
|
|
for extension in extensions {
|
|
let extension_found = extension_properties.iter().any(|ep| {
|
|
let name = unsafe { ::std::ffi::CStr::from_ptr(ep.extension_name.as_ptr()) };
|
|
name.to_str().unwrap_or("") == extension
|
|
});
|
|
if !extension_found {
|
|
return Err(GpuError::Other(format!("Extension {extension} not found")));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_layers_list(entry: &ash::Entry, layers: &[String]) -> GpuResult<()> {
|
|
let layer_properties = unsafe { entry.enumerate_instance_layer_properties()? };
|
|
for layer in layers {
|
|
let layer_found = layer_properties.iter().any(|lp| {
|
|
let name = unsafe { ::std::ffi::CStr::from_ptr(lp.layer_name.as_ptr()) };
|
|
name.to_str().unwrap_or("") == layer
|
|
});
|
|
if !layer_found {
|
|
return Err(GpuError::Other(format!("Layer {layer} not found")));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for Instance {
|
|
fn drop(&mut self) {
|
|
let allocation_callbacks = self.cpu_allocation_callbacks();
|
|
unsafe {
|
|
// Destroy first debug messenger if it's present.
|
|
if let Some(loader) = &self.vk_debug_utils_loader
|
|
&& self.vk_debug_messenger != vk::DebugUtilsMessengerEXT::null()
|
|
{
|
|
loader.destroy_debug_utils_messenger(self.vk_debug_messenger, allocation_callbacks);
|
|
}
|
|
|
|
// Last step after all drops of all GPU resources: destroy vulkan instance.
|
|
self.vk_instance.destroy_instance(allocation_callbacks);
|
|
}
|
|
}
|
|
}
|