ferroui_vulkan/
vulkan_exception.rs1use ash::vk;
2use std::fmt;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct VulkanException {
7 message: String,
8 result: Option<vk::Result>,
9}
10
11impl VulkanException {
12 pub fn new(message: impl Into<String>) -> Self {
14 Self { message: message.into(), result: None }
15 }
16
17 pub fn from_code(func_name: &str, res: i32) -> Self {
20 Self::from_result(func_name, vk::Result::from_raw(res))
21 }
22
23 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 pub fn message(&self) -> &str {
30 &self.message
31 }
32
33 pub fn result(&self) -> Option<vk::Result> {
35 self.result
36 }
37
38 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
53fn result_name(res: vk::Result) -> String {
55 let name = format!("{res:?}");
56 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
64pub trait VulkanExceptionExtensions {
66 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
80pub trait VulkanResultExtensions<T> {
82 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 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}