Skip to main content

ferroui_vulkan/
vulkan_exception.rs

1use ash::vk;
2use std::fmt;
3
4/// A failure reported by Vulkan or by the code driving it.
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct VulkanException {
7    message: String,
8    result: Option<vk::Result>,
9}
10
11impl VulkanException {
12    /// A failure without a result of Vulkan.
13    pub fn new(message: impl Into<String>) -> Self {
14        Self { message: message.into(), result: None }
15    }
16
17    /// The failure of the function `func_name` with the numeric result
18    /// `res`.
19    pub fn from_code(func_name: &str, res: i32) -> Self {
20        Self::from_result(func_name, vk::Result::from_raw(res))
21    }
22
23    /// The failure of the function `func_name` with the result `res`.
24    pub fn from_result(func_name: &str, res: vk::Result) -> Self {
25        Self { message: format!("{func_name} returned {}", result_name(res)), result: Some(res) }
26    }
27
28    /// The message of the failure.
29    pub fn message(&self) -> &str {
30        &self.message
31    }
32
33    /// The result of Vulkan, when the failure was reported by Vulkan.
34    pub fn result(&self) -> Option<vk::Result> {
35        self.result
36    }
37
38    /// The failure of the function `func_name`, unless its numeric result
39    /// `res` is `VK_SUCCESS`.
40    pub fn throw_on_error(func_name: &str, res: i32) -> Result<(), VulkanException> {
41        vk::Result::from_raw(res).throw_on_error(func_name)
42    }
43}
44
45impl fmt::Display for VulkanException {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.write_str(&self.message)
48    }
49}
50
51impl std::error::Error for VulkanException {}
52
53/// The name of a result as the Vulkan headers spell it.
54fn result_name(res: vk::Result) -> String {
55    let name = format!("{res:?}");
56    // A value the bindings have no name for prints as its number.
57    if name.starts_with(|c: char| c.is_ascii_uppercase()) {
58        format!("VK_{name}")
59    } else {
60        res.as_raw().to_string()
61    }
62}
63
64/// Turns every result but `VK_SUCCESS` into a failure.
65pub trait VulkanExceptionExtensions {
66    /// The failure of the function `func_name`, unless the result is
67    /// `VK_SUCCESS`.
68    fn throw_on_error(self, func_name: &str) -> Result<(), VulkanException>;
69}
70
71impl VulkanExceptionExtensions for vk::Result {
72    fn throw_on_error(self, func_name: &str) -> Result<(), VulkanException> {
73        if self != vk::Result::SUCCESS {
74            return Err(VulkanException::from_result(func_name, self));
75        }
76        Ok(())
77    }
78}
79
80/// Names the function whose result a failure is.
81pub trait VulkanResultExtensions<T> {
82    /// The value, or the failure of the function `func_name`.
83    fn throw_on_error(self, func_name: &str) -> Result<T, VulkanException>;
84}
85
86impl<T> VulkanResultExtensions<T> for Result<T, vk::Result> {
87    fn throw_on_error(self, func_name: &str) -> Result<T, VulkanException> {
88        self.map_err(|res| VulkanException::from_result(func_name, res))
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn success_is_no_failure() {
98        assert_eq!(vk::Result::SUCCESS.throw_on_error("vkCreateFence"), Ok(()));
99        assert_eq!(VulkanException::throw_on_error("vkCreateFence", 0), Ok(()));
100    }
101
102    #[test]
103    fn a_result_is_named_as_in_the_headers() {
104        let error = vk::Result::ERROR_OUT_OF_DATE_KHR.throw_on_error("vkAcquireNextImageKHR").unwrap_err();
105        assert_eq!(error.message(), "vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR");
106        assert_eq!(error.result(), Some(vk::Result::ERROR_OUT_OF_DATE_KHR));
107        assert_eq!(error.to_string(), error.message());
108    }
109
110    #[test]
111    fn a_positive_result_is_a_failure_too() {
112        // The reference compares with VK_SUCCESS, so VK_SUBOPTIMAL_KHR and VK_INCOMPLETE fail.
113        let error = VulkanException::throw_on_error("vkQueuePresentKHR", 1000001003).unwrap_err();
114        assert_eq!(error.message(), "vkQueuePresentKHR returned VK_SUBOPTIMAL_KHR");
115    }
116
117    #[test]
118    fn a_result_without_a_name_prints_its_number() {
119        let error = VulkanException::from_code("vkFoo", -123456);
120        assert_eq!(error.message(), "vkFoo returned -123456");
121    }
122
123    #[test]
124    fn a_plain_message_has_no_result() {
125        let error = VulkanException::new("No devices found");
126        assert_eq!(error.message(), "No devices found");
127        assert_eq!(error.result(), None);
128    }
129}