pub use crate::fns::EntryFunctions;
use crate::{
instance::{InstanceExtensions, LayerProperties},
ExtensionProperties, SafeDeref, Version, VulkanError,
};
use libloading::{Error as LibloadingError, Library};
use std::{
error::Error,
ffi::{CStr, CString},
fmt::{Debug, Display, Error as FmtError, Formatter},
mem::transmute,
os::raw::c_char,
path::Path,
ptr,
sync::Arc,
};
#[derive(Debug)]
pub struct VulkanLibrary {
loader: Box<dyn Loader>,
fns: EntryFunctions,
api_version: Version,
extension_properties: Vec<ExtensionProperties>,
supported_extensions: InstanceExtensions,
}
impl VulkanLibrary {
pub fn new() -> Result<Arc<Self>, LoadingError> {
#[cfg(target_os = "ios")]
#[allow(non_snake_case)]
fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> {
let loader = crate::statically_linked_vulkan_loader!();
Ok(Box::new(loader))
}
#[cfg(not(target_os = "ios"))]
fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> {
#[cfg(windows)]
fn get_paths() -> [&'static Path; 1] {
[Path::new("vulkan-1.dll")]
}
#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
fn get_paths() -> [&'static Path; 1] {
[Path::new("libvulkan.so.1")]
}
#[cfg(target_os = "macos")]
fn get_paths() -> [&'static Path; 3] {
[
Path::new("libvulkan.dylib"),
Path::new("libvulkan.1.dylib"),
Path::new("libMoltenVK.dylib"),
]
}
#[cfg(target_os = "android")]
fn get_paths() -> [&'static Path; 2] {
[Path::new("libvulkan.so.1"), Path::new("libvulkan.so")]
}
let paths = get_paths();
let mut err: Option<LoadingError> = None;
for path in paths {
match unsafe { DynamicLibraryLoader::new(path) } {
Ok(library) => return Ok(Box::new(library)),
Err(e) => err = Some(e),
}
}
Err(err.unwrap())
}
def_loader_impl().and_then(VulkanLibrary::with_loader)
}
pub fn with_loader(loader: impl Loader + 'static) -> Result<Arc<Self>, LoadingError> {
let fns = EntryFunctions::load(|name| unsafe {
loader
.get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr())
.map_or(ptr::null(), |func| func as _)
});
let api_version = unsafe { Self::get_api_version(&loader)? };
let extension_properties = unsafe { Self::get_extension_properties(&fns, None)? };
let supported_extensions = extension_properties
.iter()
.map(|property| property.extension_name.as_str())
.collect();
Ok(Arc::new(VulkanLibrary {
loader: Box::new(loader),
fns,
api_version,
extension_properties,
supported_extensions,
}))
}
unsafe fn get_api_version(loader: &impl Loader) -> Result<Version, VulkanError> {
let name = CStr::from_bytes_with_nul_unchecked(b"vkEnumerateInstanceVersion\0");
let func = loader.get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr());
let version = if let Some(func) = func {
let func: ash::vk::PFN_vkEnumerateInstanceVersion = transmute(func);
let mut api_version = 0;
func(&mut api_version).result().map_err(VulkanError::from)?;
Version::from(api_version)
} else {
Version {
major: 1,
minor: 0,
patch: 0,
}
};
Ok(version)
}
unsafe fn get_extension_properties(
fns: &EntryFunctions,
layer: Option<&str>,
) -> Result<Vec<ExtensionProperties>, VulkanError> {
let layer_vk = layer.map(|layer| CString::new(layer).unwrap());
loop {
let mut count = 0;
(fns.v1_0.enumerate_instance_extension_properties)(
layer_vk
.as_ref()
.map_or(ptr::null(), |layer| layer.as_ptr()),
&mut count,
ptr::null_mut(),
)
.result()
.map_err(VulkanError::from)?;
let mut output = Vec::with_capacity(count as usize);
let result = (fns.v1_0.enumerate_instance_extension_properties)(
layer_vk
.as_ref()
.map_or(ptr::null(), |layer| layer.as_ptr()),
&mut count,
output.as_mut_ptr(),
);
match result {
ash::vk::Result::SUCCESS => {
output.set_len(count as usize);
return Ok(output.into_iter().map(Into::into).collect());
}
ash::vk::Result::INCOMPLETE => (),
err => return Err(VulkanError::from(err)),
}
}
}
#[inline]
pub fn fns(&self) -> &EntryFunctions {
&self.fns
}
#[inline]
pub fn api_version(&self) -> Version {
self.api_version
}
#[inline]
pub fn extension_properties(&self) -> &[ExtensionProperties] {
&self.extension_properties
}
#[inline]
pub fn supported_extensions(&self) -> &InstanceExtensions {
&self.supported_extensions
}
pub fn layer_properties(
&self,
) -> Result<impl ExactSizeIterator<Item = LayerProperties>, VulkanError> {
let fns = self.fns();
let layer_properties = unsafe {
loop {
let mut count = 0;
(fns.v1_0.enumerate_instance_layer_properties)(&mut count, ptr::null_mut())
.result()
.map_err(VulkanError::from)?;
let mut properties = Vec::with_capacity(count as usize);
let result = (fns.v1_0.enumerate_instance_layer_properties)(
&mut count,
properties.as_mut_ptr(),
);
match result {
ash::vk::Result::SUCCESS => {
properties.set_len(count as usize);
break properties;
}
ash::vk::Result::INCOMPLETE => (),
err => return Err(VulkanError::from(err)),
}
}
};
Ok(layer_properties
.into_iter()
.map(|p| LayerProperties { props: p }))
}
#[inline]
pub fn layer_extension_properties(
&self,
layer: &str,
) -> Result<Vec<ExtensionProperties>, VulkanError> {
unsafe { Self::get_extension_properties(&self.fns, Some(layer)) }
}
#[inline]
pub fn supported_layer_extensions(
&self,
layer: &str,
) -> Result<InstanceExtensions, VulkanError> {
Ok(self
.layer_extension_properties(layer)?
.iter()
.map(|property| property.extension_name.as_str())
.collect())
}
#[inline]
pub fn supported_extensions_with_layers<'a>(
&self,
layers: impl IntoIterator<Item = &'a str>,
) -> Result<InstanceExtensions, VulkanError> {
layers
.into_iter()
.try_fold(self.supported_extensions, |extensions, layer| {
self.supported_layer_extensions(layer)
.map(|layer_extensions| extensions.union(&layer_extensions))
})
}
#[inline]
pub unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> ash::vk::PFN_vkVoidFunction {
self.loader.get_instance_proc_addr(instance, name)
}
}
pub unsafe trait Loader: Send + Sync {
unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> ash::vk::PFN_vkVoidFunction;
}
unsafe impl<T> Loader for T
where
T: SafeDeref + Send + Sync,
T::Target: Loader,
{
unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> ash::vk::PFN_vkVoidFunction {
(**self).get_instance_proc_addr(instance, name)
}
}
impl Debug for dyn Loader {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
f.debug_struct("Loader").finish_non_exhaustive()
}
}
pub struct DynamicLibraryLoader {
_vk_lib: Library,
get_instance_proc_addr: ash::vk::PFN_vkGetInstanceProcAddr,
}
impl DynamicLibraryLoader {
pub unsafe fn new(path: impl AsRef<Path>) -> Result<DynamicLibraryLoader, LoadingError> {
let vk_lib = Library::new(path.as_ref()).map_err(LoadingError::LibraryLoadFailure)?;
let get_instance_proc_addr = *vk_lib
.get(b"vkGetInstanceProcAddr")
.map_err(LoadingError::LibraryLoadFailure)?;
Ok(DynamicLibraryLoader {
_vk_lib: vk_lib,
get_instance_proc_addr,
})
}
}
unsafe impl Loader for DynamicLibraryLoader {
#[inline]
unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> ash::vk::PFN_vkVoidFunction {
(self.get_instance_proc_addr)(instance, name)
}
}
#[macro_export]
macro_rules! statically_linked_vulkan_loader {
() => {{
extern "C" {
fn vkGetInstanceProcAddr(
instance: ash::vk::Instance,
pName: *const c_char,
) -> ash::vk::PFN_vkVoidFunction;
}
struct StaticallyLinkedVulkanLoader;
unsafe impl Loader for StaticallyLinkedVulkanLoader {
unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> ash::vk::PFN_vkVoidFunction {
vkGetInstanceProcAddr(instance, name)
}
}
StaticallyLinkedVulkanLoader
}};
}
#[derive(Debug)]
pub enum LoadingError {
LibraryLoadFailure(LibloadingError),
VulkanError(VulkanError),
}
impl Error for LoadingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::VulkanError(err) => Some(err),
_ => None,
}
}
}
impl Display for LoadingError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::LibraryLoadFailure(_) => write!(f, "failed to load the Vulkan shared library"),
Self::VulkanError(err) => write!(f, "a runtime error occurred: {err}"),
}
}
}
impl From<VulkanError> for LoadingError {
fn from(err: VulkanError) -> Self {
Self::VulkanError(err)
}
}
#[cfg(test)]
mod tests {
use super::{DynamicLibraryLoader, LoadingError};
#[test]
fn dl_open_error() {
unsafe {
match DynamicLibraryLoader::new("_non_existing_library.void") {
Err(LoadingError::LibraryLoadFailure(_)) => (),
_ => panic!(),
}
}
}
}