Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support linking Vulkan directly #457

Merged
merged 8 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --all-targets
args: --workspace --all-targets --all-features
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved

generated:
name: Generated
Expand Down Expand Up @@ -47,6 +47,8 @@ jobs:
name: Test Suite
runs-on: ubuntu-latest
steps:
- name: Install Vulkan loader
run: sudo apt-get install libvulkan-dev
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ pub fn create_command_pool(&self,
let pool = device.create_command_pool(&pool_create_info).unwrap();
```

### Optional linking

The default `linked` cargo feature will link your binary with the Vulkan loader directly and expose the infallible `Entry::new`.
If your application can handle Vulkan being missing at runtime, you can instead enable the `loaded` feature to dynamically load Vulkan with `Entry::load`.

## Example
You can find the examples [here](https://github.com/MaikKlein/ash/tree/master/examples).
All examples currently require: the LunarG Validation layers and a Vulkan library that is visible in your `PATH`. An easy way to get started is to use the [LunarG Vulkan SDK](https://lunarg.com/vulkan-sdk/)
Expand Down
2 changes: 1 addition & 1 deletion ash-window/examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.build(&events_loop)?;

unsafe {
let entry = ash::Entry::new()?;
let entry = ash::Entry::new();
let surface_extensions = ash_window::enumerate_required_extensions(&window)?;
let instance_extensions = surface_extensions
.iter()
Expand Down
6 changes: 3 additions & 3 deletions ash-window/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ash::{extensions::khr, prelude::*, vk, EntryCustom, Instance};
use ash::{extensions::khr, prelude::*, vk, Entry, Instance};
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use std::ffi::CStr;

Expand All @@ -14,8 +14,8 @@ use ash::extensions::ext; // portability extensions
/// In order for the created [`vk::SurfaceKHR`] to be valid for the duration of its
/// usage, the [`Instance`] this was called on must be dropped later than the
/// resulting [`vk::SurfaceKHR`].
pub unsafe fn create_surface<L>(
entry: &EntryCustom<L>,
pub unsafe fn create_surface(
entry: &Entry,
instance: &Instance,
window_handle: &dyn HasRawWindowHandle,
allocation_callbacks: Option<&vk::AllocationCallbacks>,
Expand Down
10 changes: 9 additions & 1 deletion ash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ edition = "2018"
libloading = { version = "0.7", optional = true }

[features]
default = ["libloading"]
default = ["linked"]
# Link the Vulkan loader at compile time.
linked = []
# Support searching for the Vulkan loader manually at runtime.
loaded = ["libloading"]

[package.metadata.release]
no-dev-version = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
24 changes: 24 additions & 0 deletions ash/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
fn main() {
#[cfg(feature = "linked")]
{
use std::env;

let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
let target_pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();

println!("cargo:rerun-if-env-changed=VULKAN_SDK");
if let Ok(var) = env::var("VULKAN_SDK") {
let suffix = match (&*target_family, &*target_pointer_width) {
("windows", "32") => "Lib32",
("windows", "64") => "Lib",
_ => "lib",
};
println!("cargo:rustc-link-search={}/{}", var, suffix);
}
let lib = match &*target_family {
"windows" => "vulkan-1",
_ => "vulkan",
};
println!("cargo:rustc-link-lib={}", lib);
MaikKlein marked this conversation as resolved.
Show resolved Hide resolved
}
}
219 changes: 176 additions & 43 deletions ash/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,152 @@ use crate::instance::Instance;
use crate::prelude::*;
use crate::vk;
use crate::RawPtr;
use std::error::Error;
use std::ffi::CStr;
use std::fmt;
#[cfg(feature = "loaded")]
use std::ffi::OsStr;
use std::mem;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::ptr;
#[cfg(feature = "loaded")]
use std::sync::Arc;

/// Holds a custom type `L` to load symbols from (usually a handle to a `dlopen`ed library),
/// the [`vkGetInstanceProcAddr`][vk::StaticFn::get_instance_proc_addr()] loader function from
/// this library (in [`vk::StaticFn`]), and Vulkan's "entry point" functions (resolved with `NULL`
/// `instance`) as listed in [`vkGetInstanceProcAddr`'s description].
///
/// [`vkGetInstanceProcAddr`'s description]: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetInstanceProcAddr.html#_description
#[cfg(feature = "loaded")]
use libloading::Library;

/// Holds the Vulkan functions independent of a particular instance
#[derive(Clone)]
pub struct EntryCustom<L> {
pub struct Entry {
static_fn: vk::StaticFn,
entry_fn_1_0: vk::EntryFnV1_0,
entry_fn_1_1: vk::EntryFnV1_1,
entry_fn_1_2: vk::EntryFnV1_2,
lib: L,
#[cfg(feature = "loaded")]
_lib_guard: Option<Arc<Library>>,
}

/// Vulkan core 1.0
#[allow(non_camel_case_types)]
impl<L> EntryCustom<L> {
pub fn new_custom<Load>(
mut lib: L,
mut load: Load,
) -> std::result::Result<Self, MissingEntryPoint>
where
Load: FnMut(&mut L, &::std::ffi::CStr) -> *const c_void,
{
// Bypass the normal StaticFn::load so we can return an error
let static_fn = vk::StaticFn::load_checked(|name| load(&mut lib, name))?;
impl Entry {
/// Load entry points from a Vulkan loader linked at compile time
///
/// Note that instance/device functions are still fetched via `vkGetInstanceProcAddr` and
/// `vkGetDeviceProcAddr` for maximum performance.
///
/// ```no_run
/// use ash::{vk, Entry};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let entry = Entry::new();
/// let app_info = vk::ApplicationInfo {
/// api_version: vk::make_api_version(0, 1, 0, 0),
/// ..Default::default()
/// };
/// let create_info = vk::InstanceCreateInfo {
/// p_application_info: &app_info,
/// ..Default::default()
/// };
/// let instance = unsafe { entry.create_instance(&create_info, None)? };
/// # Ok(()) }
/// ```
#[cfg(feature = "linked")]
#[cfg_attr(docsrs, doc(cfg(feature = "linked")))]
pub fn new() -> Self {
// Sound because we're linking to Vulkan, which provides a vkGetInstanceProcAddr that has
// defined behavior in this use.
unsafe {
Self::from_static_fn(vk::StaticFn {
get_instance_proc_addr: vkGetInstanceProcAddr,
})
}
}

/// Load default Vulkan library for the current platform
///
/// # Safety
/// `dlopen`ing native libraries is inherently unsafe. The safety guidelines
/// for [`Library::new()`] and [`Library::get()`] apply here.
///
/// ```no_run
/// use ash::{vk, Entry};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let entry = unsafe { Entry::load()? };
/// let app_info = vk::ApplicationInfo {
/// api_version: vk::make_api_version(0, 1, 0, 0),
/// ..Default::default()
/// };
/// let create_info = vk::InstanceCreateInfo {
/// p_application_info: &app_info,
/// ..Default::default()
/// };
/// let instance = unsafe { entry.create_instance(&create_info, None)? };
/// # Ok(()) }
/// ```
#[cfg(feature = "loaded")]
#[cfg_attr(docsrs, doc(cfg(feature = "loaded")))]
pub unsafe fn load() -> Result<Self, LoadingError> {
#[cfg(windows)]
const LIB_PATH: &str = "vulkan-1.dll";

#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
const LIB_PATH: &str = "libvulkan.so.1";

#[cfg(target_os = "android")]
const LIB_PATH: &str = "libvulkan.so";

#[cfg(any(target_os = "macos", target_os = "ios"))]
const LIB_PATH: &str = "libvulkan.dylib";

Self::load_from(LIB_PATH)
}

/// Load Vulkan library at `path`
///
/// # Safety
/// `dlopen`ing native libraries is inherently unsafe. The safety guidelines
/// for [`Library::new()`] and [`Library::get()`] apply here.
#[cfg(feature = "loaded")]
#[cfg_attr(docsrs, doc(cfg(feature = "loaded")))]
pub unsafe fn load_from(path: impl AsRef<OsStr>) -> Result<Self, LoadingError> {
let lib = Library::new(path)
.map_err(LoadingError::LibraryLoadFailure)
.map(Arc::new)?;

let static_fn = vk::StaticFn::load_checked(|name| {
lib.get(name.to_bytes_with_nul())
.map(|symbol| *symbol)
.unwrap_or(ptr::null_mut())
})?;

Ok(Self {
_lib_guard: Some(lib),
..Self::from_static_fn(static_fn)
})
}

/// Load entry points based on an already-loaded [`vk::StaticFn`]
///
/// # Safety
/// `static_fn` must contain valid function pointers that comply with the semantics specified by
/// Vulkan 1.0, which must remain valid for at least the lifetime of the returned [`Entry`].
pub unsafe fn from_static_fn(static_fn: vk::StaticFn) -> Self {
Ralith marked this conversation as resolved.
Show resolved Hide resolved
let load_fn = |name: &std::ffi::CStr| unsafe {
mem::transmute(static_fn.get_instance_proc_addr(vk::Instance::null(), name.as_ptr()))
};
let entry_fn_1_0 = vk::EntryFnV1_0::load(load_fn);
let entry_fn_1_1 = vk::EntryFnV1_1::load(load_fn);
let entry_fn_1_2 = vk::EntryFnV1_2::load(load_fn);

Ok(EntryCustom {
Self {
static_fn,
entry_fn_1_0,
entry_fn_1_1,
entry_fn_1_2,
lib,
})
#[cfg(feature = "loaded")]
_lib_guard: None,
}
}

pub fn fp_v1_0(&self) -> &vk::EntryFnV1_0 {
Expand All @@ -65,7 +162,7 @@ impl<L> EntryCustom<L> {
/// ```no_run
/// # use ash::{Entry, vk};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let entry = unsafe { Entry::new() }?;
/// let entry = Entry::new();
/// match entry.try_enumerate_instance_version()? {
/// // Vulkan 1.1+
/// Some(version) => {
Expand Down Expand Up @@ -107,16 +204,15 @@ impl<L> EntryCustom<L> {
&self,
create_info: &vk::InstanceCreateInfo,
allocation_callbacks: Option<&vk::AllocationCallbacks>,
) -> Result<Instance, InstanceError> {
) -> VkResult<Instance> {
Ralith marked this conversation as resolved.
Show resolved Hide resolved
let mut instance = mem::zeroed();
self.entry_fn_1_0
.create_instance(
create_info,
allocation_callbacks.as_raw_ptr(),
&mut instance,
)
.result()
.map_err(InstanceError::VkError)?;
.result()?;
Ok(Instance::load(&self.static_fn, instance))
}

Expand Down Expand Up @@ -168,7 +264,7 @@ impl<L> EntryCustom<L> {

/// Vulkan core 1.1
#[allow(non_camel_case_types)]
impl<L> EntryCustom<L> {
impl Entry {
pub fn fp_v1_1(&self) -> &vk::EntryFnV1_1 {
&self.entry_fn_1_1
}
Expand All @@ -189,29 +285,19 @@ impl<L> EntryCustom<L> {

/// Vulkan core 1.2
#[allow(non_camel_case_types)]
impl<L> EntryCustom<L> {
impl Entry {
pub fn fp_v1_2(&self) -> &vk::EntryFnV1_2 {
&self.entry_fn_1_2
}
}

#[derive(Clone, Debug)]
pub enum InstanceError {
LoadError(Vec<&'static str>),
VkError(vk::Result),
}

impl fmt::Display for InstanceError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InstanceError::LoadError(e) => write!(f, "{}", e.join("; ")),
InstanceError::VkError(e) => write!(f, "{}", e),
}
#[cfg(feature = "linked")]
impl Default for Entry {
fn default() -> Self {
Self::new()
}
}

impl Error for InstanceError {}

impl vk::StaticFn {
pub fn load_checked<F>(mut _f: F) -> Result<Self, MissingEntryPoint>
where
Expand Down Expand Up @@ -242,3 +328,50 @@ impl std::fmt::Display for MissingEntryPoint {
}
}
impl std::error::Error for MissingEntryPoint {}

#[cfg(feature = "linked")]
extern "system" {
fn vkGetInstanceProcAddr(instance: vk::Instance, name: *const c_char)
-> vk::PFN_vkVoidFunction;
}

#[cfg(feature = "loaded")]
mod loaded {
use std::error::Error;
use std::fmt;

use super::*;

#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "loaded")))]
pub enum LoadingError {
LibraryLoadFailure(libloading::Error),
MissingEntryPoint(MissingEntryPoint),
}

impl fmt::Display for LoadingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LoadingError::LibraryLoadFailure(err) => fmt::Display::fmt(err, f),
LoadingError::MissingEntryPoint(err) => fmt::Display::fmt(err, f),
}
}
}

impl Error for LoadingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(match self {
LoadingError::LibraryLoadFailure(err) => err,
LoadingError::MissingEntryPoint(err) => err,
})
}
}

impl From<MissingEntryPoint> for LoadingError {
fn from(err: MissingEntryPoint) -> Self {
LoadingError::MissingEntryPoint(err)
}
}
}
#[cfg(feature = "loaded")]
pub use self::loaded::*;
Loading