blob: 7714fd29c731d23ceb7e61c0bd6b6305453de2e7 [file] [log] [blame]
// 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 implementation loading system.
//!
//! Before vulkano can do anything, it first needs to find 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 struct that implements `Loader`, you can create a `FunctionPointers` struct
//! from it and use this `FunctionPointers` struct to build an `Instance`.
//!
//! By default vulkano will use the `auto_loader()` function, which tries to automatically load
//! a Vulkan implementation from the system.
use crate::check_errors;
use crate::fns::EntryFunctions;
use crate::OomError;
use crate::SafeDeref;
use crate::Version;
use lazy_static::lazy_static;
use shared_library;
use std::error;
use std::ffi::CStr;
use std::fmt;
use std::mem;
use std::ops::Deref;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::path::Path;
/// Implemented on objects that grant access to a Vulkan implementation.
pub unsafe trait Loader {
/// Calls the `vkGetInstanceProcAddr` function. The parameters are the same.
///
/// The returned function must stay valid for as long as `self` is alive.
fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> *const c_void;
}
unsafe impl<T> Loader for T
where
T: SafeDeref,
T::Target: Loader,
{
#[inline]
fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> *const c_void {
(**self).get_instance_proc_addr(instance, name)
}
}
/// Implementation of `Loader` that loads Vulkan from a dynamic library.
pub struct DynamicLibraryLoader {
vk_lib: shared_library::dynamic_library::DynamicLibrary,
get_proc_addr:
extern "system" fn(instance: ash::vk::Instance, pName: *const c_char) -> *const c_void,
}
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<P>(path: P) -> Result<DynamicLibraryLoader, LoadingError>
where
P: AsRef<Path>,
{
let vk_lib = shared_library::dynamic_library::DynamicLibrary::open(Some(path.as_ref()))
.map_err(LoadingError::LibraryLoadFailure)?;
let get_proc_addr = {
let ptr: *mut c_void = vk_lib
.symbol("vkGetInstanceProcAddr")
.map_err(|_| LoadingError::MissingEntryPoint("vkGetInstanceProcAddr".to_owned()))?;
mem::transmute(ptr)
};
Ok(DynamicLibraryLoader {
vk_lib,
get_proc_addr,
})
}
}
unsafe impl Loader for DynamicLibraryLoader {
#[inline]
fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> *const c_void {
(self.get_proc_addr)(instance, name)
}
}
/// Wraps around a loader and contains function pointers.
pub struct FunctionPointers<L> {
loader: L,
fns: EntryFunctions,
}
impl<L> FunctionPointers<L> {
/// Loads some global function pointer from the loader.
pub fn new(loader: L) -> FunctionPointers<L>
where
L: Loader,
{
let fns = EntryFunctions::load(|name| unsafe {
mem::transmute(loader.get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr()))
});
FunctionPointers { loader, fns }
}
/// Returns the collection of Vulkan entry points from the Vulkan loader.
#[inline]
pub fn fns(&self) -> &EntryFunctions {
&self.fns
}
/// Returns the highest Vulkan version that is supported for instances.
pub fn api_version(&self) -> Result<Version, OomError>
where
L: Loader,
{
// 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.
unsafe {
let name = CStr::from_bytes_with_nul_unchecked(b"vkEnumerateInstanceVersion\0");
let func = self.get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr());
if func.is_null() {
Ok(Version {
major: 1,
minor: 0,
patch: 0,
})
} else {
type Pfn = extern "system" fn(pApiVersion: *mut u32) -> ash::vk::Result;
let func: Pfn = mem::transmute(func);
let mut api_version = 0;
check_errors(func(&mut api_version))?;
Ok(Version::from(api_version))
}
}
}
/// Calls `get_instance_proc_addr` on the underlying loader.
#[inline]
pub fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> *const c_void
where
L: Loader,
{
self.loader.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 {
fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> extern "system" fn() -> () {
unsafe { vkGetInstanceProcAddr(instance, name) }
}
}
StaticallyLinkedVulkanLoader
}};
}
/// Returns the default `FunctionPointers` for this system.
///
/// This function tries to auto-guess where to find the Vulkan implementation, and loads it in a
/// `lazy_static!`. The content of the lazy_static is then returned, or an error if we failed to
/// load Vulkan.
pub fn auto_loader(
) -> Result<&'static FunctionPointers<Box<dyn Loader + Send + Sync>>, LoadingError> {
#[cfg(target_os = "ios")]
#[allow(non_snake_case)]
fn def_loader_impl() -> Result<Box<Loader + Send + Sync>, LoadingError> {
let loader = statically_linked_vulkan_loader!();
Ok(Box::new(loader))
}
#[cfg(not(target_os = "ios"))]
fn def_loader_impl() -> Result<Box<dyn Loader + Send + Sync>, 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))
}
lazy_static! {
static ref DEFAULT_LOADER: Result<FunctionPointers<Box<dyn Loader + Send + Sync>>, LoadingError> =
def_loader_impl().map(FunctionPointers::new);
}
match DEFAULT_LOADER.deref() {
&Ok(ref ptr) => Ok(ptr),
&Err(ref err) => Err(err.clone()),
}
}
/// Error that can happen when loading the Vulkan loader.
#[derive(Debug, Clone)]
pub enum LoadingError {
/// Failed to load the Vulkan shared library.
LibraryLoadFailure(String), // TODO: meh for error type, but this needs changes in shared_library
/// One of the entry points required to be supported by the Vulkan implementation is missing.
MissingEntryPoint(String),
}
impl error::Error for LoadingError {
/*#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
LoadingError::LibraryLoadFailure(ref err) => Some(err),
_ => None
}
}*/
}
impl fmt::Display for LoadingError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
fmt,
"{}",
match *self {
LoadingError::LibraryLoadFailure(_) => "failed to load the Vulkan shared library",
LoadingError::MissingEntryPoint(_) => {
"one of the entry points required to be supported by the Vulkan implementation \
is missing"
}
}
)
}
}
#[cfg(test)]
mod tests {
use crate::instance::loader::DynamicLibraryLoader;
use crate::instance::loader::LoadingError;
#[test]
fn dl_open_error() {
unsafe {
match DynamicLibraryLoader::new("_non_existing_library.void") {
Err(LoadingError::LibraryLoadFailure(_)) => (),
_ => panic!(),
}
}
}
}