use ash::vk;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VulkanException {
message: String,
result: Option<vk::Result>,
}
impl VulkanException {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into(), result: None }
}
pub fn from_code(func_name: &str, res: i32) -> Self {
Self::from_result(func_name, vk::Result::from_raw(res))
}
pub fn from_result(func_name: &str, res: vk::Result) -> Self {
Self { message: format!("{func_name} returned {}", result_name(res)), result: Some(res) }
}
pub fn message(&self) -> &str {
&self.message
}
pub fn result(&self) -> Option<vk::Result> {
self.result
}
pub fn throw_on_error(func_name: &str, res: i32) -> Result<(), VulkanException> {
vk::Result::from_raw(res).throw_on_error(func_name)
}
}
impl fmt::Display for VulkanException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for VulkanException {}
fn result_name(res: vk::Result) -> String {
let name = format!("{res:?}");
if name.starts_with(|c: char| c.is_ascii_uppercase()) {
format!("VK_{name}")
} else {
res.as_raw().to_string()
}
}
pub trait VulkanExceptionExtensions {
fn throw_on_error(self, func_name: &str) -> Result<(), VulkanException>;
}
impl VulkanExceptionExtensions for vk::Result {
fn throw_on_error(self, func_name: &str) -> Result<(), VulkanException> {
if self != vk::Result::SUCCESS {
return Err(VulkanException::from_result(func_name, self));
}
Ok(())
}
}
pub trait VulkanResultExtensions<T> {
fn throw_on_error(self, func_name: &str) -> Result<T, VulkanException>;
}
impl<T> VulkanResultExtensions<T> for Result<T, vk::Result> {
fn throw_on_error(self, func_name: &str) -> Result<T, VulkanException> {
self.map_err(|res| VulkanException::from_result(func_name, res))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success_is_no_failure() {
assert_eq!(vk::Result::SUCCESS.throw_on_error("vkCreateFence"), Ok(()));
assert_eq!(VulkanException::throw_on_error("vkCreateFence", 0), Ok(()));
}
#[test]
fn a_result_is_named_as_in_the_headers() {
let error = vk::Result::ERROR_OUT_OF_DATE_KHR.throw_on_error("vkAcquireNextImageKHR").unwrap_err();
assert_eq!(error.message(), "vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR");
assert_eq!(error.result(), Some(vk::Result::ERROR_OUT_OF_DATE_KHR));
assert_eq!(error.to_string(), error.message());
}
#[test]
fn a_positive_result_is_a_failure_too() {
let error = VulkanException::throw_on_error("vkQueuePresentKHR", 1000001003).unwrap_err();
assert_eq!(error.message(), "vkQueuePresentKHR returned VK_SUBOPTIMAL_KHR");
}
#[test]
fn a_result_without_a_name_prints_its_number() {
let error = VulkanException::from_code("vkFoo", -123456);
assert_eq!(error.message(), "vkFoo returned -123456");
}
#[test]
fn a_plain_message_has_no_result() {
let error = VulkanException::new("No devices found");
assert_eq!(error.message(), "No devices found");
assert_eq!(error.result(), None);
}
}