Skip to main content

vk/load/
entry.rs

1//! Dynamic library ownership and checked Vulkan global dispatch.
2use crate::core::Instance;
3use crate::{core as vk, core::make_api_version};
4use libloading::Library;
5use std::{
6	ffi::{CStr, OsStr},
7	sync::Arc,
8};
9
10/// Loading or required-command resolution failure.
11#[derive(Debug, PartialEq)]
12pub enum LoadError {
13	InvalidMetadata(&'static str),
14	/// Native library opening or symbol lookup failed.
15	LibraryOpen(String),
16	/// A required Vulkan function could not be resolved.
17	MissingSymbol(&'static str),
18	/// Import never transfers ownership of a null native handle.
19	NullHandle(&'static str),
20}
21
22impl std::fmt::Display for LoadError {
23	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24		match self {
25			Self::InvalidMetadata(s) => write!(f, "invalid Vulkan import metadata: {s}"),
26			Self::LibraryOpen(s) => write!(f, "Vulkan loader: {s}"),
27			Self::MissingSymbol(s) => write!(f, "missing Vulkan command: {s}"),
28			Self::NullHandle(s) => write!(f, "null Vulkan {s} handle"),
29		}
30	}
31}
32
33impl std::error::Error for LoadError {}
34
35/// A Vulkan driver failure or a missing command (entry, instance or device level).
36#[derive(Debug, PartialEq)]
37pub enum CallError {
38	/// The native enumeration did not stabilize within the bounded retry budget.
39	EnumerationUnstable {
40		attempts: u32,
41	},
42	/// Native creation succeeded but cleanup could not be resolved. Handle this
43	/// variant explicitly to recover the still-live object and retained library.
44	InitializationFailed(super::InitializationFailure),
45	Vulkan(vk::Result),
46	MissingCommand(&'static str),
47	/// Invalid caller-supplied creation/import metadata; no native effect occurred.
48	InvalidMetadata(&'static str),
49	/// A required exact feature-record member is disabled or unrecorded.
50	FeatureNotEnabled {
51		structure: vk::StructureType,
52		member: &'static str,
53	},
54	HostAllocationFailed,
55	InvalidOutput(&'static str),
56	CountOverflow(usize),
57	/// An output/parallel slice does not match its native input count.
58	SliceLengthMismatch {
59		parameter: &'static str,
60		expected: usize,
61		actual: usize,
62	},
63}
64
65impl std::fmt::Display for CallError {
66	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67		match self {
68			Self::EnumerationUnstable { attempts } => write!(
69				f,
70				"Vulkan enumeration did not stabilize after {attempts} attempts"
71			),
72			Self::InitializationFailed(failure) => failure.fmt(f),
73			Self::Vulkan(r) => write!(f, "Vulkan result {}", r.as_raw()),
74			Self::MissingCommand(n) => write!(f, "missing Vulkan command: {n}"),
75			Self::InvalidMetadata(reason) => write!(f, "invalid Vulkan metadata: {reason}"),
76			Self::FeatureNotEnabled { structure, member } => write!(
77				f,
78				"Vulkan feature {structure:?}::{member} is not recorded as enabled"
79			),
80			Self::HostAllocationFailed => f.write_str("Vulkan host allocation failed"),
81			Self::InvalidOutput(reason) => write!(f, "invalid Vulkan output: {reason}"),
82			Self::CountOverflow(count) => write!(f, "Vulkan count exceeds u32: {count}"),
83			Self::SliceLengthMismatch {
84				parameter,
85				expected,
86				actual,
87			} => {
88				write!(f, "Vulkan {parameter} length {actual}, expected {expected}")
89			}
90		}
91	}
92}
93
94impl std::error::Error for CallError {
95	fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
96		match self {
97			Self::InitializationFailed(failure) => Some(failure),
98			_ => None,
99		}
100	}
101}
102
103impl From<vk::Result> for CallError {
104	/// Lift a non-success `VkResult` into a `CallError`.
105	///
106	/// Callers that only expect `VK_SUCCESS` can use `?` on a `vk::Result` once
107	/// it is inside a `Result<_, CallError>` context.
108	fn from(r: vk::Result) -> Self {
109		Self::Vulkan(r)
110	}
111}
112
113/// Global dispatch. Instances created here retain the native library independently.
114#[derive(Clone)]
115pub struct Entry {
116	pub(crate) library: Option<Arc<Library>>,
117	pub(crate) gipa: unsafe extern "system" fn(
118		vk::InstanceHandle,
119		*const std::ffi::c_char,
120	) -> vk::PFN_vkVoidFunction,
121	fp: vk::FnEntry,
122}
123
124impl Entry {
125	/// Load the platform's default Vulkan library.
126	///
127	/// # Safety
128	/// The library and its initialization routines must be safe to load, and it
129	/// must implement the Vulkan ABI. Raw copied PFNs must not outlive all owners.
130	pub unsafe fn new() -> Result<Self, LoadError> {
131		#[cfg(windows)]
132		let path = "vulkan-1.dll";
133		#[cfg(any(target_os = "macos", target_os = "ios"))]
134		let path = "libvulkan.dylib";
135		#[cfg(any(target_os = "android", target_os = "fuchsia"))]
136		let path = "libvulkan.so";
137		#[cfg(not(any(
138			windows,
139			target_os = "macos",
140			target_os = "ios",
141			target_os = "android",
142			target_os = "fuchsia"
143		)))]
144		let path = "libvulkan.so.1";
145		// SAFETY: caller provides the native-loading guarantees above.
146		unsafe { Self::new_from_path(path) }
147	}
148
149	/// Load a Vulkan implementation at the supplied native path.
150	///
151	/// # Safety
152	/// The library's initialization/finalization must be safe, and its exports
153	/// must implement Vulkan. Raw copied PFNs must not outlive all library owners.
154	pub unsafe fn new_from_path(path: impl AsRef<OsStr>) -> Result<Self, LoadError> {
155		// SAFETY: caller guarantees the library and its ABI are valid.
156		let library =
157			Arc::new(unsafe { Library::new(path) }.map_err(|e| LoadError::LibraryOpen(e.to_string()))?);
158		// SAFETY: the Vulkan ABI specifies this exact export and signature.
159		let gipa = unsafe {
160			library.get::<unsafe extern "system" fn(
161				vk::InstanceHandle,
162				*const std::ffi::c_char,
163			) -> vk::PFN_vkVoidFunction>(b"vkGetInstanceProcAddr\0")
164		}
165		.map_err(|_| LoadError::MissingSymbol("vkGetInstanceProcAddr"))?;
166		// SAFETY: the resolved function belongs to the retained native library.
167		let mut entry = unsafe { Self::new_from_resolver(*gipa) }?;
168		entry.library = Some(library);
169		Ok(entry)
170	}
171
172	/// Construct dispatch using an externally owned resolver.
173	///
174	/// # Safety
175	/// The resolver must implement Vulkan and remain callable for this entry and
176	/// all instances and raw PFNs derived from it, including any entry clones.
177	pub unsafe fn new_from_resolver(
178		gipa: unsafe extern "system" fn(
179			vk::InstanceHandle,
180			*const std::ffi::c_char,
181		) -> vk::PFN_vkVoidFunction,
182	) -> Result<Self, LoadError> {
183		// SAFETY: caller guarantees a Vulkan-conformant resolver.
184		let fp = unsafe { vk::FnEntry::new_from_resolver(gipa) };
185		for (present, name) in [
186			(fp.create_instance.is_some(), "vkCreateInstance"),
187			(
188				fp.enumerate_instance_extension_properties.is_some(),
189				"vkEnumerateInstanceExtensionProperties",
190			),
191			(
192				fp.enumerate_instance_layer_properties.is_some(),
193				"vkEnumerateInstanceLayerProperties",
194			),
195		] {
196			if !present {
197				return Err(LoadError::MissingSymbol(name));
198			}
199		}
200		Ok(Self {
201			library: None,
202			gipa,
203			fp,
204		})
205	}
206
207	/// Borrow the immutable global dispatch table. Copied functions remain unsafe.
208	pub fn fp(&self) -> &vk::FnEntry {
209		&self.fp
210	}
211
212	/// Resolve a command for a live instance, or a global command with NULL.
213	///
214	/// # Safety
215	/// `instance` must be NULL or a live instance from this implementation.
216	/// The caller must retain the library while using the returned function.
217	pub unsafe fn get_instance_proc_addr(
218		&self,
219		instance: vk::InstanceHandle,
220		name: &CStr,
221	) -> vk::PFN_vkVoidFunction {
222		// SAFETY: caller guarantees the instance belongs to this implementation.
223		unsafe { (self.gipa)(instance, name.as_ptr()) }
224	}
225
226	/// Create a headless or extension-configured Vulkan instance.
227	///
228	/// # Safety
229	/// The create info, its reachable pointers and allocator callbacks must meet
230	/// Vulkan valid usage. Allocator state must remain alive until destruction.
231	pub unsafe fn create_instance(
232		&self,
233		create_info: &vk::InstanceCreateInfo,
234		allocator: Option<&vk::AllocationCallbacks>,
235	) -> Result<Instance, CallError> {
236		// SAFETY: the caller guarantees the canonical creation contract.
237		unsafe { crate::core::create_instance(self, create_info, allocator) }
238	}
239}
240
241use crate::core::command::commands;
242commands! { Entry, entry;
243	/// Enumerate instance extensions available without any layer.
244	///
245	/// Pass `layer_name = None` for the implicit layer (ICD + loader). Pass a
246	/// layer name to query extensions exposed by that specific layer.
247	///
248	/// # Errors
249	/// Returns `CallError::MissingCommand` if the driver predates Vulkan 1.0
250	/// (practically impossible) or `CallError::Vulkan` on a driver failure.
251	pub fn enumerate_instance_extension_properties(
252		layer_name: Option<&std::ffi::CStr>,
253	) -> Result<Vec<vk::ExtensionProperties>, CallError> {
254		let f = entry
255			.fp
256			.enumerate_instance_extension_properties
257			.ok_or(CallError::MissingCommand(
258				"vkEnumerateInstanceExtensionProperties",
259			))?;
260		let layer_ptr = layer_name.map_or(std::ptr::null(), |l| l.as_ptr());
261		// SAFETY: validated Vulkan PFN; the optional layer name remains live.
262		unsafe { super::enumerate::enumerate(|count, data| f(layer_ptr, count, data)) }
263	}
264
265	/// Enumerate all implicit and explicit Vulkan layers installed on this system.
266	///
267	/// # Errors
268	/// Returns `CallError::MissingCommand` if the command is absent or
269	/// `CallError::Vulkan` on a driver failure.
270	pub fn enumerate_instance_layer_properties() -> Result<Vec<vk::LayerProperties>, CallError> {
271		let f = entry
272			.fp
273			.enumerate_instance_layer_properties
274			.ok_or(CallError::MissingCommand(
275				"vkEnumerateInstanceLayerProperties",
276			))?;
277		// SAFETY: validated Vulkan PFN implements the count/data protocol.
278		unsafe { super::enumerate::enumerate(|count, data| f(count, data)) }
279	}
280
281	/// Query the loader API version. A Vulkan 1.0 loader has no version command.
282	pub fn enumerate_instance_version() -> Result<u32, vk::Result> {
283		let Some(f) = entry.fp.enumerate_instance_version else {
284			return Ok(make_api_version(0, 1, 0, 0));
285		};
286		let mut version = 0;
287		// SAFETY: the table was checked on construction and the output is writable.
288		let result = unsafe { f(&mut version) };
289		if result == vk::Result::SUCCESS {
290			Ok(version)
291		} else {
292			Err(result)
293		}
294	}
295}