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::enums::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::enums::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::enums::Result>;
55
56/// Convert a raw `vk::enums::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::enums::Result) -> VkResult<()> {
62    if result.as_raw() >= 0 {
63        Ok(())
64    } else {
65        Err(result)
66    }
67}
68
69/// Error returned when the Vulkan shared library cannot be loaded.
70///
71/// This is distinct from `vk::enums::Result`, it represents a failure to reach
72/// the Vulkan API at all, not a Vulkan API error.
73///
74/// # Examples
75///
76/// ```
77/// use vulkan_rust::LoadError;
78///
79/// let err = LoadError::MissingEntryPoint;
80/// assert_eq!(
81///     err.to_string(),
82///     "vkGetInstanceProcAddr not found in Vulkan library",
83/// );
84/// ```
85#[derive(Debug)]
86pub enum LoadError {
87    /// The Vulkan shared library could not be found or opened.
88    Library(libloading::Error),
89    /// `vkGetInstanceProcAddr` could not be resolved from the library.
90    MissingEntryPoint,
91}
92
93impl std::fmt::Display for LoadError {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self {
96            LoadError::Library(e) => write!(f, "failed to load Vulkan library: {e}"),
97            LoadError::MissingEntryPoint => {
98                f.write_str("vkGetInstanceProcAddr not found in Vulkan library")
99            }
100        }
101    }
102}
103
104impl std::error::Error for LoadError {
105    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
106        match self {
107            LoadError::Library(e) => Some(e),
108            LoadError::MissingEntryPoint => None,
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn check_success_returns_ok() {
119        assert!(check(vk::enums::Result::SUCCESS).is_ok());
120    }
121
122    #[test]
123    fn check_negative_returns_err() {
124        let result = check(vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY);
125        assert_eq!(result, Err(vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY));
126    }
127
128    #[test]
129    fn check_non_zero_success_codes_return_ok() {
130        // INCOMPLETE and SUBOPTIMAL are non-negative success codes.
131        assert!(check(vk::enums::Result::INCOMPLETE).is_ok());
132        assert!(check(vk::enums::Result::SUBOPTIMAL).is_ok());
133    }
134
135    #[test]
136    fn check_extension_error_codes_return_err() {
137        // Extension error codes (promoted to core) must be negative.
138        assert!(check(vk::enums::Result::ERROR_OUT_OF_POOL_MEMORY).is_err());
139        assert!(check(vk::enums::Result::ERROR_SURFACE_LOST).is_err());
140        assert!(check(vk::enums::Result::ERROR_VALIDATION_FAILED).is_err());
141    }
142
143    #[test]
144    fn enumerate_two_call_returns_items() {
145        // Simulate a Vulkan enumerate command that returns 3 u32 values.
146        let result = enumerate_two_call(|count, data: *mut u32| {
147            unsafe { *count = 3 };
148            if !data.is_null() {
149                unsafe {
150                    *data = 10;
151                    *data.add(1) = 20;
152                    *data.add(2) = 30;
153                }
154            }
155            vk::enums::Result::SUCCESS
156        });
157        assert_eq!(result.expect("should succeed"), vec![10u32, 20, 30]);
158    }
159
160    #[test]
161    fn enumerate_two_call_returns_empty_on_zero_count() {
162        let result = enumerate_two_call::<u32>(|count, _data| {
163            unsafe { *count = 0 };
164            vk::enums::Result::SUCCESS
165        });
166        assert!(result.expect("should succeed").is_empty());
167    }
168
169    #[test]
170    fn enumerate_two_call_propagates_error() {
171        let result =
172            enumerate_two_call::<u32>(|_count, _data| vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY);
173        assert_eq!(
174            result.unwrap_err(),
175            vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
176        );
177    }
178
179    #[test]
180    fn fill_two_call_returns_items() {
181        let result = fill_two_call(|count, data: *mut u64| {
182            unsafe { *count = 2 };
183            if !data.is_null() {
184                unsafe {
185                    *data = 42u64;
186                    *data.add(1) = 99;
187                }
188            }
189        });
190        assert_eq!(result, vec![42u64, 99]);
191    }
192
193    #[test]
194    fn fill_two_call_returns_empty_on_zero_count() {
195        let result = fill_two_call::<u32>(|count, _data| {
196            unsafe { *count = 0 };
197        });
198        assert!(result.is_empty());
199    }
200
201    #[test]
202    #[cfg(not(miri))] // libloading calls FFI that Miri cannot interpret
203    fn load_error_source_library_returns_some() {
204        let lib_err =
205            unsafe { libloading::Library::new("nonexistent_vulkan_lib.dll") }.unwrap_err();
206        let err = LoadError::Library(lib_err);
207        assert!(
208            std::error::Error::source(&err).is_some(),
209            "Library variant should have a source"
210        );
211    }
212
213    #[test]
214    fn load_error_source_missing_entry_point_returns_none() {
215        let err = LoadError::MissingEntryPoint;
216        assert!(
217            std::error::Error::source(&err).is_none(),
218            "MissingEntryPoint should have no source"
219        );
220    }
221
222    #[test]
223    fn load_error_display_missing_entry_point() {
224        let err = LoadError::MissingEntryPoint;
225        assert_eq!(
226            err.to_string(),
227            "vkGetInstanceProcAddr not found in Vulkan library"
228        );
229    }
230
231    #[test]
232    #[cfg(not(miri))] // libloading calls FFI that Miri cannot interpret
233    fn load_error_display_library() {
234        // Trigger a real libloading error by loading a nonexistent library.
235        let lib_err =
236            unsafe { libloading::Library::new("nonexistent_vulkan_lib.dll") }.unwrap_err();
237        let err = LoadError::Library(lib_err);
238        assert!(err.to_string().contains("failed to load Vulkan library"));
239    }
240}