diff options
Diffstat (limited to 'src/instance/debug.rs')
-rw-r--r-- | src/instance/debug.rs | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/instance/debug.rs b/src/instance/debug.rs new file mode 100644 index 0000000..2bc0615 --- /dev/null +++ b/src/instance/debug.rs @@ -0,0 +1,461 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! Debug callback called by intermediate layers or by the driver. +//! +//! When working on an application, it is recommended to register a debug callback. For example if +//! you enable the validation layers provided by the official Vulkan SDK, they will warn you about +//! invalid API usages or performance problems by calling this callback. The callback can also +//! be called by the driver or by whatever intermediate layer is activated. +//! +//! Note that the vulkano library can also emit messages to warn you about performance issues. +//! TODO: ^ that's not the case yet, need to choose whether we keep this idea +//! +//! # Example +//! +//! ``` +//! # use vulkano::instance::Instance; +//! # use std::sync::Arc; +//! # let instance: Arc<Instance> = return; +//! use vulkano::instance::debug::DebugCallback; +//! +//! let _callback = DebugCallback::errors_and_warnings(&instance, |msg| { +//! println!("Debug callback: {:?}", msg.description); +//! }).ok(); +//! ``` +//! +//! The type of `msg` in the callback is [`Message`](struct.Message.html). +//! +//! Note that you must keep the `_callback` object alive for as long as you want your callback to +//! be callable. If you don't store the return value of `DebugCallback`'s constructor in a +//! variable, it will be immediately destroyed and your callback will not work. +//! + +use crate::check_errors; +use crate::instance::Instance; +use crate::Error; +use crate::VulkanObject; +use std::error; +use std::ffi::CStr; +use std::fmt; +use std::mem::MaybeUninit; +use std::os::raw::c_void; +use std::panic; +use std::ptr; +use std::sync::Arc; + +/// Registration of a callback called by validation layers. +/// +/// The callback can be called as long as this object is alive. +#[must_use = "The DebugCallback object must be kept alive for as long as you want your callback \ + to be called"] +pub struct DebugCallback { + instance: Arc<Instance>, + debug_report_callback: ash::vk::DebugUtilsMessengerEXT, + user_callback: Box<Box<dyn Fn(&Message) + Send>>, +} + +impl DebugCallback { + /// Initializes a debug callback. + /// + /// Panics generated by calling `user_callback` are ignored. + pub fn new<F>( + instance: &Arc<Instance>, + severity: MessageSeverity, + ty: MessageType, + user_callback: F, + ) -> Result<DebugCallback, DebugCallbackCreationError> + where + F: Fn(&Message) + 'static + Send + panic::RefUnwindSafe, + { + if !instance.enabled_extensions().ext_debug_utils { + return Err(DebugCallbackCreationError::MissingExtension); + } + + // Note that we need to double-box the callback, because a `*const Fn()` is a fat pointer + // that can't be cast to a `*const c_void`. + let user_callback = Box::new(Box::new(user_callback) as Box<_>); + + unsafe extern "system" fn callback( + severity: ash::vk::DebugUtilsMessageSeverityFlagsEXT, + ty: ash::vk::DebugUtilsMessageTypeFlagsEXT, + callback_data: *const ash::vk::DebugUtilsMessengerCallbackDataEXT, + user_data: *mut c_void, + ) -> ash::vk::Bool32 { + let user_callback = user_data as *mut Box<dyn Fn()> as *const _; + let user_callback: &Box<dyn Fn(&Message)> = &*user_callback; + + let layer_prefix = (*callback_data) + .p_message_id_name + .as_ref() + .map(|msg_id_name| { + CStr::from_ptr(msg_id_name) + .to_str() + .expect("debug callback message not utf-8") + }); + + let description = CStr::from_ptr((*callback_data).p_message) + .to_str() + .expect("debug callback message not utf-8"); + + let message = Message { + severity: MessageSeverity { + information: !(severity & ash::vk::DebugUtilsMessageSeverityFlagsEXT::INFO) + .is_empty(), + warning: !(severity & ash::vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) + .is_empty(), + error: !(severity & ash::vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) + .is_empty(), + verbose: !(severity & ash::vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE) + .is_empty(), + }, + ty: MessageType { + general: !(ty & ash::vk::DebugUtilsMessageTypeFlagsEXT::GENERAL).is_empty(), + validation: !(ty & ash::vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION) + .is_empty(), + performance: !(ty & ash::vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE) + .is_empty(), + }, + layer_prefix, + description, + }; + + // Since we box the closure, the type system doesn't detect that the `UnwindSafe` + // bound is enforced. Therefore we enforce it manually. + let _ = panic::catch_unwind(panic::AssertUnwindSafe(move || { + user_callback(&message); + })); + + ash::vk::FALSE + } + + let severity = { + let mut flags = ash::vk::DebugUtilsMessageSeverityFlagsEXT::empty(); + if severity.information { + flags |= ash::vk::DebugUtilsMessageSeverityFlagsEXT::INFO; + } + if severity.warning { + flags |= ash::vk::DebugUtilsMessageSeverityFlagsEXT::WARNING; + } + if severity.error { + flags |= ash::vk::DebugUtilsMessageSeverityFlagsEXT::ERROR; + } + if severity.verbose { + flags |= ash::vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE; + } + flags + }; + + let ty = { + let mut flags = ash::vk::DebugUtilsMessageTypeFlagsEXT::empty(); + if ty.general { + flags |= ash::vk::DebugUtilsMessageTypeFlagsEXT::GENERAL; + } + if ty.validation { + flags |= ash::vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION; + } + if ty.performance { + flags |= ash::vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE; + } + flags + }; + + let infos = ash::vk::DebugUtilsMessengerCreateInfoEXT { + flags: ash::vk::DebugUtilsMessengerCreateFlagsEXT::empty(), + message_severity: severity, + message_type: ty, + pfn_user_callback: Some(callback), + p_user_data: &*user_callback as &Box<_> as *const Box<_> as *const c_void as *mut _, + ..Default::default() + }; + + let fns = instance.fns(); + + let debug_report_callback = unsafe { + let mut output = MaybeUninit::uninit(); + check_errors(fns.ext_debug_utils.create_debug_utils_messenger_ext( + instance.internal_object(), + &infos, + ptr::null(), + output.as_mut_ptr(), + ))?; + output.assume_init() + }; + + Ok(DebugCallback { + instance: instance.clone(), + debug_report_callback, + user_callback, + }) + } + + /// Initializes a debug callback with errors and warnings. + /// + /// Shortcut for `new(instance, MessageTypes::errors_and_warnings(), user_callback)`. + #[inline] + pub fn errors_and_warnings<F>( + instance: &Arc<Instance>, + user_callback: F, + ) -> Result<DebugCallback, DebugCallbackCreationError> + where + F: Fn(&Message) + Send + 'static + panic::RefUnwindSafe, + { + DebugCallback::new( + instance, + MessageSeverity::errors_and_warnings(), + MessageType::general(), + user_callback, + ) + } +} + +impl Drop for DebugCallback { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.instance.fns(); + fns.ext_debug_utils.destroy_debug_utils_messenger_ext( + self.instance.internal_object(), + self.debug_report_callback, + ptr::null(), + ); + } + } +} + +/// A message received by the callback. +pub struct Message<'a> { + /// Severity of message. + pub severity: MessageSeverity, + /// Type of message, + pub ty: MessageType, + /// Prefix of the layer that reported this message or `None` if unknown. + pub layer_prefix: Option<&'a str>, + /// Description of the message. + pub description: &'a str, +} + +/// Severity of message. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct MessageSeverity { + /// An error that may cause undefined results, including an application crash. + pub error: bool, + /// An unexpected use. + pub warning: bool, + /// An informational message that may be handy when debugging an application. + pub information: bool, + /// Diagnostic information from the loader and layers. + pub verbose: bool, +} + +impl MessageSeverity { + /// Builds a `MessageSeverity` with all fields set to `false` expect `error`. + #[inline] + pub const fn errors() -> MessageSeverity { + MessageSeverity { + error: true, + ..MessageSeverity::none() + } + } + + /// Builds a `MessageSeverity` with all fields set to `false` expect `warning`. + #[inline] + pub const fn warnings() -> MessageSeverity { + MessageSeverity { + warning: true, + ..MessageSeverity::none() + } + } + + /// Builds a `MessageSeverity` with all fields set to `false` expect `information`. + #[inline] + pub const fn information() -> MessageSeverity { + MessageSeverity { + information: true, + ..MessageSeverity::none() + } + } + + /// Builds a `MessageSeverity` with all fields set to `false` expect `verbose`. + #[inline] + pub const fn verbose() -> MessageSeverity { + MessageSeverity { + verbose: true, + ..MessageSeverity::none() + } + } + + /// Builds a `MessageSeverity` with all fields set to `false` expect `error`, `warning` + /// and `performance_warning`. + #[inline] + pub const fn errors_and_warnings() -> MessageSeverity { + MessageSeverity { + error: true, + warning: true, + ..MessageSeverity::none() + } + } + + /// Builds a `MessageSeverity` with all fields set to `false`. + #[inline] + pub const fn none() -> MessageSeverity { + MessageSeverity { + error: false, + warning: false, + information: false, + verbose: false, + } + } + + /// Builds a `MessageSeverity` with all fields set to `true`. + #[inline] + pub const fn all() -> MessageSeverity { + MessageSeverity { + error: true, + warning: true, + information: true, + verbose: true, + } + } +} + +impl std::ops::BitOr for MessageSeverity { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + MessageSeverity { + error: self.error | rhs.error, + warning: self.warning | rhs.warning, + information: self.information | rhs.information, + verbose: self.verbose | rhs.verbose, + } + } +} + +/// Type of message. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct MessageType { + /// Specifies that some general event has occurred. + pub general: bool, + /// Specifies that something has occurred during validation against the vulkan specification + pub validation: bool, + /// Specifies a potentially non-optimal use of Vulkan + pub performance: bool, +} + +impl MessageType { + /// Builds a `MessageType` with general field set to `true`. + #[inline] + pub const fn general() -> MessageType { + MessageType { + general: true, + validation: false, + performance: false, + } + } + + /// Builds a `MessageType` with validation field set to `true`. + #[inline] + pub const fn validation() -> MessageType { + MessageType { + general: false, + validation: true, + performance: false, + } + } + + /// Builds a `MessageType` with performance field set to `true`. + #[inline] + pub const fn performance() -> MessageType { + MessageType { + general: false, + validation: false, + performance: true, + } + } + + /// Builds a `MessageType` with all fields set to `true`. + #[inline] + pub const fn all() -> MessageType { + MessageType { + general: true, + validation: true, + performance: true, + } + } + + /// Builds a `MessageType` with all fields set to `false`. + #[inline] + pub const fn none() -> MessageType { + MessageType { + general: false, + validation: false, + performance: false, + } + } +} + +impl std::ops::BitOr for MessageType { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + MessageType { + general: self.general | rhs.general, + validation: self.validation | rhs.validation, + performance: self.performance | rhs.performance, + } + } +} + +/// Error that can happen when creating a debug callback. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DebugCallbackCreationError { + /// The `EXT_debug_utils` extension was not enabled. + MissingExtension, +} + +impl error::Error for DebugCallbackCreationError {} + +impl fmt::Display for DebugCallbackCreationError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + DebugCallbackCreationError::MissingExtension => { + "the `EXT_debug_utils` extension was not enabled" + } + } + ) + } +} + +impl From<Error> for DebugCallbackCreationError { + #[inline] + fn from(err: Error) -> DebugCallbackCreationError { + panic!("unexpected error: {:?}", err) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + #[test] + fn ensure_sendable() { + // It's useful to be able to initialize a DebugCallback on one thread + // and keep it alive on another thread. + let instance = instance!(); + let severity = MessageSeverity::none(); + let ty = MessageType::all(); + let callback = DebugCallback::new(&instance, severity, ty, |_| {}); + thread::spawn(move || { + let _ = callback; + }); + } +} |