blob: d2e3abe7b117c5773fbb37013622b501ca229c96 [file] [log] [blame] [edit]
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Vulkan library loading system.
//!
//! Before Vulkano can do anything, it first needs to find a library containing an implementation
//! of Vulkan. A Vulkan implementation is defined as a single `vkGetInstanceProcAddr` function,
//! which can be accessed through the `Loader` trait.
//!
//! This module provides various implementations of the `Loader` trait.
//!
//! Once you have a type that implements `Loader`, you can create a `VulkanLibrary`
//! from it and use this `VulkanLibrary` struct to build an `Instance`.
pub use crate::fns::EntryFunctions;
use crate::{
instance::{InstanceExtensions, LayerProperties},
ExtensionProperties, OomError, 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,
};
/// A loaded library containing a valid Vulkan implementation.
#[derive(Debug)]
pub struct VulkanLibrary {
loader: Box<dyn Loader>,
fns: EntryFunctions,
api_version: Version,
extension_properties: Vec<ExtensionProperties>,
supported_extensions: InstanceExtensions,
}
impl VulkanLibrary {
/// Loads the default Vulkan library for this system.
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_path() -> &'static Path {
Path::new("vulkan-1.dll")
}
#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
fn get_path() -> &'static Path {
Path::new("libvulkan.so.1")
}
#[cfg(target_os = "macos")]
fn get_path() -> &'static Path {
Path::new("libvulkan.1.dylib")
}
#[cfg(target_os = "android")]
fn get_path() -> &'static Path {
Path::new("libvulkan.so")
}
let loader = unsafe { DynamicLibraryLoader::new(get_path())? };
Ok(Box::new(loader))
}
def_loader_impl().and_then(VulkanLibrary::with_loader)
}
/// Loads a custom Vulkan library.
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> {
// Per the Vulkan spec:
// If the vkGetInstanceProcAddr returns NULL for vkEnumerateInstanceVersion, it is a
// Vulkan 1.0 implementation. Otherwise, the application can call vkEnumerateInstanceVersion
// to determine the version of Vulkan.
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)),
}
}
}
/// Returns pointers to the raw global Vulkan functions of the library.
#[inline]
pub fn fns(&self) -> &EntryFunctions {
&self.fns
}
/// Returns the highest Vulkan version that is supported for instances.
#[inline]
pub fn api_version(&self) -> Version {
self.api_version
}
/// Returns the extension properties reported by the core library.
#[inline]
pub fn extension_properties(&self) -> &[ExtensionProperties] {
&self.extension_properties
}
/// Returns the extensions that are supported by the core library.
#[inline]
pub fn supported_extensions(&self) -> &InstanceExtensions {
&self.supported_extensions
}
/// Returns the list of layers that are available when creating an instance.
///
/// On success, this function returns an iterator that produces
/// [`LayerProperties`](crate::instance::LayerProperties) objects. In order to enable a layer,
/// you need to pass its name (returned by `LayerProperties::name()`) when creating the
/// [`Instance`](crate::instance::Instance).
///
/// > **Note**: The available layers may change between successive calls to this function, so
/// > each call may return different results. It is possible that one of the layers enumerated
/// > here is no longer available when you create the `Instance`. This will lead to an error
/// > when calling `Instance::new`.
///
/// # Examples
///
/// ```no_run
/// use vulkano::VulkanLibrary;
///
/// let library = VulkanLibrary::new().unwrap();
///
/// for layer in library.layer_properties().unwrap() {
/// println!("Available layer: {}", layer.name());
/// }
/// ```
pub fn layer_properties(
&self,
) -> Result<impl ExactSizeIterator<Item = LayerProperties>, OomError> {
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).into()),
}
}
};
Ok(layer_properties
.into_iter()
.map(|p| LayerProperties { props: p }))
}
/// Returns the extension properties that are reported by the given layer.
#[inline]
pub fn layer_extension_properties(
&self,
layer: &str,
) -> Result<Vec<ExtensionProperties>, VulkanError> {
unsafe { Self::get_extension_properties(&self.fns, Some(layer)) }
}
/// Returns the extensions that are supported by the given 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())
}
/// Returns the union of the extensions that are supported by the core library and all
/// the given layers.
#[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))
})
}
/// Calls `get_instance_proc_addr` on the underlying loader.
#[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)
}
}
/// Implemented on objects that grant access to a Vulkan implementation.
pub unsafe trait Loader: Send + Sync {
/// Calls the `vkGetInstanceProcAddr` function. The parameters are the same.
///
/// The returned function must stay valid for as long as `self` is alive.
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> {
Ok(())
}
}
/// Implementation of `Loader` that loads Vulkan from a dynamic library.
pub struct DynamicLibraryLoader {
_vk_lib: Library,
get_instance_proc_addr: ash::vk::PFN_vkGetInstanceProcAddr,
}
impl DynamicLibraryLoader {
/// Tries to load the dynamic library at the given path, and tries to
/// load `vkGetInstanceProcAddr` in it.
///
/// # Safety
///
/// - The dynamic library must be a valid Vulkan implementation.
///
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)
}
}
/// Expression that returns a loader that assumes that Vulkan is linked to the executable you're
/// compiling.
///
/// If you use this macro, you must linked to a library that provides the `vkGetInstanceProcAddr`
/// symbol.
///
/// This is provided as a macro and not as a regular function, because the macro contains an
/// `extern {}` block.
// TODO: should this be unsafe?
#[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
}};
}
/// Error that can happen when loading a Vulkan library.
#[derive(Debug)]
pub enum LoadingError {
/// Failed to load the Vulkan shared library.
LibraryLoadFailure(LibloadingError),
/// Not enough memory.
OomError(OomError),
}
impl Error for LoadingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
//Self::LibraryLoadFailure(err) => Some(err),
Self::OomError(err) => Some(err),
_ => None,
}
}
}
impl Display for LoadingError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(
f,
"{}",
match self {
Self::LibraryLoadFailure(_) => "failed to load the Vulkan shared library",
Self::OomError(_) => "not enough memory available",
}
)
}
}
impl From<VulkanError> for LoadingError {
fn from(err: VulkanError) -> Self {
match err {
err @ VulkanError::OutOfHostMemory => Self::OomError(OomError::from(err)),
err @ VulkanError::OutOfDeviceMemory => Self::OomError(OomError::from(err)),
_ => panic!("unexpected error: {:?}", 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!(),
}
}
}
}