use ash::vk;
use std::ffi::{c_void, CStr};
use std::sync::Mutex;
pub const VALIDATION_LAYER: &str = "VK_LAYER_KHRONOS_validation";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationMessage {
pub severity: ValidationSeverity,
pub id: String,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ValidationSeverity {
Info,
Warning,
Error,
}
#[derive(Debug, Default)]
pub struct ValidationLog {
messages: Mutex<Vec<ValidationMessage>>,
}
impl ValidationLog {
pub fn record(&self, message: ValidationMessage) {
if let Ok(mut guard) = self.messages.lock() {
guard.push(message);
}
}
pub fn messages(&self) -> Vec<ValidationMessage> {
self.messages.lock().map(|g| g.clone()).unwrap_or_default()
}
pub fn errors(&self) -> Vec<ValidationMessage> {
self.messages()
.into_iter()
.filter(|m| m.severity == ValidationSeverity::Error)
.collect()
}
pub fn is_clean(&self) -> bool {
self.errors().is_empty()
}
}
pub(crate) unsafe extern "system" fn debug_callback(
severity: vk::DebugUtilsMessageSeverityFlagsEXT,
_types: vk::DebugUtilsMessageTypeFlagsEXT,
data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
user_data: *mut c_void,
) -> vk::Bool32 {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if data.is_null() || user_data.is_null() {
return;
}
let data = unsafe { &*data };
let log = unsafe { &*(user_data as *const ValidationLog) };
let severity = if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
ValidationSeverity::Error
} else if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
ValidationSeverity::Warning
} else {
ValidationSeverity::Info
};
let cstr_or_empty = |p: *const std::ffi::c_char| {
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
};
log.record(ValidationMessage {
severity,
id: cstr_or_empty(data.p_message_id_name),
message: cstr_or_empty(data.p_message),
});
}));
let _ = result;
vk::FALSE
}
pub(crate) fn messenger_create_info<'a>() -> vk::DebugUtilsMessengerCreateInfoEXT<'a> {
vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
)
.pfn_user_callback(Some(debug_callback))
}
pub struct Validated(std::mem::ManuallyDrop<crate::VulkanContext>);
impl Validated {
pub fn new(device: crate::DevicePreference) -> impeller_hal::Result<Self> {
crate::VulkanContext::with_config(crate::ContextConfig {
device,
validation: true,
})
.map(|ctx| Self(std::mem::ManuallyDrop::new(ctx)))
}
}
impl std::ops::Deref for Validated {
type Target = crate::VulkanContext;
fn deref(&self) -> &crate::VulkanContext {
&self.0
}
}
impl std::ops::DerefMut for Validated {
fn deref_mut(&mut self) -> &mut crate::VulkanContext {
&mut self.0
}
}
impl Drop for Validated {
fn drop(&mut self) {
let panicking = std::thread::panicking();
let active = self.0.validation_active();
let log = self.0.validation_log();
unsafe { std::mem::ManuallyDrop::drop(&mut self.0) };
if panicking {
return;
}
if !active {
eprintln!("skipping: the validation layer is unavailable, so API use went unchecked");
return;
}
let errors = log.errors();
assert!(errors.is_empty(), "validation errors: {errors:?}");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_log_with_only_warnings_is_still_clean() {
let log = ValidationLog::default();
log.record(ValidationMessage {
severity: ValidationSeverity::Warning,
id: "test".into(),
message: "a warning".into(),
});
assert!(log.is_clean());
assert_eq!(log.messages().len(), 1);
assert!(log.errors().is_empty());
}
#[test]
fn errors_are_separated_from_the_rest() {
let log = ValidationLog::default();
for severity in [
ValidationSeverity::Info,
ValidationSeverity::Warning,
ValidationSeverity::Error,
] {
log.record(ValidationMessage {
severity,
id: "id".into(),
message: format!("{severity:?}"),
});
}
assert!(!log.is_clean());
assert_eq!(log.errors().len(), 1);
assert_eq!(log.errors()[0].severity, ValidationSeverity::Error);
assert_eq!(log.messages().len(), 3);
}
}