aboutsummaryrefslogtreecommitdiff
path: root/src/library.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/library.rs')
-rw-r--r--src/library.rs464
1 files changed, 464 insertions, 0 deletions
diff --git a/src/library.rs b/src/library.rs
new file mode 100644
index 0000000..d2e3abe
--- /dev/null
+++ b/src/library.rs
@@ -0,0 +1,464 @@
+// 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!(),
+ }
+ }
+ }
+}