Skip to main content

vulkan_rust/
error.rs

1use crate::vk;
2
3/// Two-call enumerate pattern used by many Vulkan commands that return `VkResult`.
4///
5/// First call with null data pointer to get the count, allocate, then call
6/// again to fill the buffer.
7pub(crate) fn enumerate_two_call<T>(
8    call: impl Fn(*mut u32, *mut T) -> vk::Result,
9) -> VkResult<Vec<T>> {
10    let mut count = 0u32;
11    check(call(&mut count, std::ptr::null_mut()))?;
12    let mut data = Vec::with_capacity(count as usize);
13    let result = call(&mut count, data.as_mut_ptr());
14    check(result)?;
15    // SAFETY: the Vulkan command wrote `count` elements into `data`'s spare capacity.
16    unsafe { data.set_len(count as usize) };
17    Ok(data)
18}
19
20/// Two-call fill pattern for Vulkan commands that return `void` (no `VkResult`).
21///
22/// Used by commands like `vkGetPhysicalDeviceQueueFamilyProperties` which write
23/// directly into the output buffer without a result code.
24pub(crate) fn fill_two_call<T>(call: impl Fn(*mut u32, *mut T)) -> Vec<T> {
25    let mut count = 0u32;
26    call(&mut count, std::ptr::null_mut());
27    let mut data = Vec::with_capacity(count as usize);
28    call(&mut count, data.as_mut_ptr());
29    // SAFETY: the Vulkan command wrote `count` elements into `data`'s spare capacity.
30    unsafe { data.set_len(count as usize) };
31    data
32}
33
34/// Vulkan API result type.
35///
36/// The `Err` variant is any negative `vk::Result` (an error code).
37/// Non-negative codes (including `SUCCESS`, `INCOMPLETE`, `SUBOPTIMAL`)
38/// are treated as success.
39///
40/// # Examples
41///
42/// ```
43/// use vulkan_rust::VkResult;
44/// use vulkan_rust::vk;
45///
46/// fn do_vulkan_work() -> VkResult<u32> {
47///     // Simulate a successful Vulkan call.
48///     Ok(42)
49/// }
50///
51/// let result = do_vulkan_work();
52/// assert!(result.is_ok());
53/// ```
54pub type VkResult<T> = std::result::Result<T, vk::Result>;
55
56/// Convert a raw `vk::Result` into `VkResult<()>`.
57///
58/// Vulkan defines success codes as non-negative and error codes as negative.
59/// Commands that need to distinguish specific success codes (e.g. `INCOMPLETE`
60/// for enumeration) handle that explicitly after calling this.
61pub(crate) fn check(result: vk::Result) -> VkResult<()> {
62    if result.as_raw() >= 0 {
63        Ok(())
64    } else {
65        Err(result)
66    }
67}
68
69/// Wrapper around [`vk::Result`] that implements [`std::error::Error`].
70///
71/// `vk::Result` is a generated `#[repr(transparent)]` newtype without
72/// `Display` or `Error` impls. This wrapper bridges that gap so Vulkan error
73/// codes can participate in `Box<dyn Error>` chains.
74///
75/// # Examples
76///
77/// ```
78/// use vulkan_rust::VkError;
79/// use vulkan_rust::vk;
80///
81/// let err = VkError(vk::Result::ERROR_OUT_OF_HOST_MEMORY);
82/// assert_eq!(err.to_string(), "ERROR_OUT_OF_HOST_MEMORY");
83/// ```
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct VkError(pub vk::Result);
86
87impl std::fmt::Display for VkError {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        write!(f, "{:?}", self.0)
90    }
91}
92
93impl std::error::Error for VkError {}
94
95impl From<vk::Result> for VkError {
96    fn from(r: vk::Result) -> Self {
97        Self(r)
98    }
99}
100
101/// Error returned when the Vulkan shared library cannot be loaded.
102///
103/// This is distinct from `vk::Result`, it represents a failure to reach
104/// the Vulkan API at all, not a Vulkan API error.
105///
106/// # Examples
107///
108/// ```
109/// use vulkan_rust::LoadError;
110///
111/// let err = LoadError::MissingEntryPoint;
112/// assert_eq!(
113///     err.to_string(),
114///     "vkGetInstanceProcAddr not found in Vulkan library",
115/// );
116/// ```
117#[derive(Debug)]
118pub enum LoadError {
119    /// The Vulkan shared library could not be found or opened.
120    Library(libloading::Error),
121    /// `vkGetInstanceProcAddr` could not be resolved from the library.
122    MissingEntryPoint,
123}
124
125impl std::fmt::Display for LoadError {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            LoadError::Library(e) => write!(f, "failed to load Vulkan library: {e}"),
129            LoadError::MissingEntryPoint => {
130                f.write_str("vkGetInstanceProcAddr not found in Vulkan library")
131            }
132        }
133    }
134}
135
136impl std::error::Error for LoadError {
137    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138        match self {
139            LoadError::Library(e) => Some(e),
140            LoadError::MissingEntryPoint => None,
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn check_success_returns_ok() {
151        assert!(check(vk::Result::SUCCESS).is_ok());
152    }
153
154    #[test]
155    fn check_negative_returns_err() {
156        let result = check(vk::Result::ERROR_OUT_OF_HOST_MEMORY);
157        assert_eq!(result, Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY));
158    }
159
160    #[test]
161    fn check_non_zero_success_codes_return_ok() {
162        // INCOMPLETE and SUBOPTIMAL are non-negative success codes.
163        assert!(check(vk::Result::INCOMPLETE).is_ok());
164        assert!(check(vk::Result::SUBOPTIMAL).is_ok());
165    }
166
167    #[test]
168    fn check_extension_error_codes_return_err() {
169        // Extension error codes (promoted to core) must be negative.
170        assert!(check(vk::Result::ERROR_OUT_OF_POOL_MEMORY).is_err());
171        assert!(check(vk::Result::ERROR_SURFACE_LOST).is_err());
172        assert!(check(vk::Result::ERROR_VALIDATION_FAILED).is_err());
173    }
174
175    #[test]
176    fn enumerate_two_call_returns_items() {
177        // Simulate a Vulkan enumerate command that returns 3 u32 values.
178        let result = enumerate_two_call(|count, data: *mut u32| {
179            unsafe { *count = 3 };
180            if !data.is_null() {
181                unsafe {
182                    *data = 10;
183                    *data.add(1) = 20;
184                    *data.add(2) = 30;
185                }
186            }
187            vk::Result::SUCCESS
188        });
189        assert_eq!(result.expect("should succeed"), vec![10u32, 20, 30]);
190    }
191
192    #[test]
193    fn enumerate_two_call_returns_empty_on_zero_count() {
194        let result = enumerate_two_call::<u32>(|count, _data| {
195            unsafe { *count = 0 };
196            vk::Result::SUCCESS
197        });
198        assert!(result.expect("should succeed").is_empty());
199    }
200
201    #[test]
202    fn enumerate_two_call_propagates_error() {
203        let result =
204            enumerate_two_call::<u32>(|_count, _data| vk::Result::ERROR_OUT_OF_HOST_MEMORY);
205        assert_eq!(result.unwrap_err(), vk::Result::ERROR_OUT_OF_HOST_MEMORY);
206    }
207
208    #[test]
209    fn fill_two_call_returns_items() {
210        let result = fill_two_call(|count, data: *mut u64| {
211            unsafe { *count = 2 };
212            if !data.is_null() {
213                unsafe {
214                    *data = 42u64;
215                    *data.add(1) = 99;
216                }
217            }
218        });
219        assert_eq!(result, vec![42u64, 99]);
220    }
221
222    #[test]
223    fn fill_two_call_returns_empty_on_zero_count() {
224        let result = fill_two_call::<u32>(|count, _data| {
225            unsafe { *count = 0 };
226        });
227        assert!(result.is_empty());
228    }
229
230    #[test]
231    #[cfg(not(miri))] // libloading calls FFI that Miri cannot interpret
232    fn load_error_source_library_returns_some() {
233        let lib_err =
234            unsafe { libloading::Library::new("nonexistent_vulkan_lib.dll") }.unwrap_err();
235        let err = LoadError::Library(lib_err);
236        assert!(
237            std::error::Error::source(&err).is_some(),
238            "Library variant should have a source"
239        );
240    }
241
242    #[test]
243    fn load_error_source_missing_entry_point_returns_none() {
244        let err = LoadError::MissingEntryPoint;
245        assert!(
246            std::error::Error::source(&err).is_none(),
247            "MissingEntryPoint should have no source"
248        );
249    }
250
251    #[test]
252    fn load_error_display_missing_entry_point() {
253        let err = LoadError::MissingEntryPoint;
254        assert_eq!(
255            err.to_string(),
256            "vkGetInstanceProcAddr not found in Vulkan library"
257        );
258    }
259
260    #[test]
261    #[cfg(not(miri))] // libloading calls FFI that Miri cannot interpret
262    fn load_error_display_library() {
263        // Trigger a real libloading error by loading a nonexistent library.
264        let lib_err =
265            unsafe { libloading::Library::new("nonexistent_vulkan_lib.dll") }.unwrap_err();
266        let err = LoadError::Library(lib_err);
267        assert!(err.to_string().contains("failed to load Vulkan library"));
268    }
269}