Skip to main content

vk/load/
entry.rs

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