Skip to main content

vk/load/
failure.rs

1//! Recovery when a driver creates an object but does not expose its destruction command.
2use crate::core as vk;
3use libloading::Library;
4use std::{ffi::CStr, sync::Arc};
5
6/// A native object returned successfully before dispatch initialization failed.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum CreatedHandle {
9	Instance(vk::InstanceHandle),
10	Device(vk::DeviceHandle),
11}
12
13/// Retains a created handle and its library when automatic rollback is impossible.
14/// The native object still needs explicit cleanup; dropping this error performs
15/// no destruction. Keep its parent and original allocator callback state alive.
16#[derive(Debug)]
17#[must_use = "a created native object still needs explicit recovery"]
18pub struct InitializationFailure {
19	pub(crate) handle: CreatedHandle,
20	pub(crate) missing_command: &'static str,
21	pub(crate) library: Option<Arc<Library>>,
22	pub(crate) instance: vk::InstanceHandle,
23	pub(crate) gipa: unsafe extern "system" fn(
24		vk::InstanceHandle,
25		*const std::ffi::c_char,
26	) -> vk::PFN_vkVoidFunction,
27	pub(crate) gdpa: vk::PFN_vkGetDeviceProcAddr,
28}
29
30impl PartialEq for InitializationFailure {
31	fn eq(&self, other: &Self) -> bool {
32		// Error equality identifies the same native resource and missing command.
33		self.handle == other.handle
34			&& self.instance == other.instance
35			&& self.missing_command == other.missing_command
36	}
37}
38
39impl std::fmt::Display for InitializationFailure {
40	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41		write!(
42			f,
43			"missing {}; {:?} requires recovery",
44			self.missing_command, self.handle
45		)
46	}
47}
48
49impl std::error::Error for InitializationFailure {}
50
51impl InitializationFailure {
52	fn exported(&self, name: &CStr) -> vk::PFN_vkVoidFunction {
53		// SAFETY: the retained library was loaded under Entry's Vulkan ABI contract.
54		// The erased pointer is restored only to the command's exact prototype.
55		unsafe {
56			self.library.as_ref().and_then(|library| {
57				library
58					.get::<unsafe extern "system" fn()>(name.to_bytes_with_nul())
59					.ok()
60					.map(|symbol| *symbol)
61			})
62		}
63	}
64
65	/// Native object retained by this failure. Do not create duplicate owners.
66	pub fn handle(&self) -> CreatedHandle {
67		self.handle
68	}
69
70	/// Required command whose absence prevented wrapper initialization.
71	pub fn missing_command(&self) -> &'static str {
72		self.missing_command
73	}
74
75	/// Retry explicit cleanup; on failure return the same owner for further recovery.
76	/// No submission or wait is performed.
77	///
78	/// # Safety
79	/// The native object and its parents must still be live, with no children or
80	/// outstanding operations. Callbacks must match creation and remain valid.
81	/// Do not separately destroy the exposed handle before calling this method.
82	pub unsafe fn destroy(self, allocator: Option<&vk::AllocationCallbacks>) -> Result<(), Self> {
83		let allocator = allocator.map_or(std::ptr::null(), |value| value);
84		// Keep the library loaded through resolution and native cleanup.
85		let _retained = &self.library;
86		match self.handle {
87			CreatedHandle::Instance(handle) => {
88				// SAFETY: caller guarantees the instance and its resolver remain valid.
89				let function = unsafe { (self.gipa)(handle, c"vkDestroyInstance".as_ptr()) }
90					.or_else(|| self.exported(c"vkDestroyInstance"));
91				// SAFETY: this exact command has the Vulkan destruction prototype.
92				let function = unsafe {
93					std::mem::transmute::<vk::PFN_vkVoidFunction, vk::PFN_vkDestroyInstance>(function)
94				};
95				let Some(function) = function else {
96					return Err(self);
97				};
98				// SAFETY: caller supplies native destruction and callback guarantees.
99				unsafe { function(handle, allocator) };
100			}
101			CreatedHandle::Device(handle) => {
102				// SAFETY: the retained resolver and caller-owned parent remain valid.
103				let function = unsafe {
104					self
105						.gdpa
106						.and_then(|load| load(handle, c"vkDestroyDevice".as_ptr()))
107						.or_else(|| (self.gipa)(self.instance, c"vkDestroyDevice".as_ptr()))
108						.or_else(|| self.exported(c"vkDestroyDevice"))
109				};
110				// SAFETY: this exact command has the Vulkan destruction prototype.
111				let function = unsafe {
112					std::mem::transmute::<vk::PFN_vkVoidFunction, vk::PFN_vkDestroyDevice>(function)
113				};
114				let Some(function) = function else {
115					return Err(self);
116				};
117				// SAFETY: caller supplies native destruction and callback guarantees.
118				unsafe { function(handle, allocator) };
119			}
120		}
121		Ok(())
122	}
123}
124
125#[cfg(test)]
126#[path = "../../../test/rs/load/test_failure.rs"]
127mod tests;