use std::ffi::c_void; use std::io::{stderr, Write}; use std::panic::catch_unwind; use std::ptr::null_mut; use std::sync::Mutex; use crate::opengl::util::gl_enum; pub type MessageCallback = fn( source: MessageSource, ty: MessageType, id: u32, severity: MessageSeverity, message: &str, ); pub fn enable_debug_output() { unsafe { gl::Enable(gl::DEBUG_OUTPUT); gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS); } } pub fn set_debug_message_callback(cb: MessageCallback) { *DEBUG_MESSAGE_CB.lock().unwrap() = Some(cb); unsafe { gl::DebugMessageCallback(Some(debug_message_cb), null_mut()); } } pub fn set_debug_message_control( source: Option, ty: Option, severity: Option, ids: &[u32], ids_action: MessageIdsAction, ) { // Ids shouldn't realistically be large enough to cause a panic here let ids_len: i32 = ids.len().try_into().unwrap(); unsafe { gl::DebugMessageControl( source.map_or(gl::DONT_CARE, |source| source as u32), ty.map_or(gl::DONT_CARE, |ty| ty as u32), severity.map_or(gl::DONT_CARE, |severity| severity as u32), ids_len, ids.as_ptr(), ids_action as u8, ); } } #[derive(Debug, Clone, Copy)] #[allow(dead_code)] pub enum MessageIdsAction { Enable = 1, Disable = 0, } gl_enum! { pub enum MessageSource { Api = gl::DEBUG_SOURCE_API, WindowSystem = gl::DEBUG_SOURCE_WINDOW_SYSTEM, ShaderCompiler = gl::DEBUG_SOURCE_SHADER_COMPILER, ThirdParty = gl::DEBUG_SOURCE_THIRD_PARTY, Application = gl::DEBUG_SOURCE_APPLICATION, Other = gl::DEBUG_SOURCE_OTHER, } } gl_enum! { pub enum MessageType { DeprecatedBehavior = gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR, Error = gl::DEBUG_TYPE_ERROR, Marker = gl::DEBUG_TYPE_MARKER, Other = gl::DEBUG_TYPE_OTHER, Performance = gl::DEBUG_TYPE_PERFORMANCE, PopGroup = gl::DEBUG_TYPE_POP_GROUP, PushGroup = gl::DEBUG_TYPE_PUSH_GROUP, Portability = gl::DEBUG_TYPE_PORTABILITY, UndefinedBehavior = gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR, } } gl_enum! { pub enum MessageSeverity { High = gl::DEBUG_SEVERITY_HIGH, Medium = gl::DEBUG_SEVERITY_MEDIUM, Low = gl::DEBUG_SEVERITY_LOW, Notification = gl::DEBUG_SEVERITY_NOTIFICATION, } } static DEBUG_MESSAGE_CB: Mutex> = Mutex::new(None); extern "system" fn debug_message_cb( source: gl::types::GLenum, ty: gl::types::GLenum, id: gl::types::GLuint, severity: gl::types::GLenum, message_length: gl::types::GLsizei, message: *const gl::types::GLchar, _user_param: *mut c_void, ) { // Unwinds are catched because unwinding from Rust code into foreign code is UB. let res = catch_unwind(|| { let cb_lock = DEBUG_MESSAGE_CB.lock().unwrap(); if let Some(cb) = *cb_lock { let msg_source = MessageSource::from_gl(source).unwrap(); let msg_type = MessageType::from_gl(ty).unwrap(); let msg_severity = MessageSeverity::from_gl(severity).unwrap(); let msg_length = usize::try_from(message_length).unwrap(); // SAFETY: The received message should be a valid ASCII string let message = unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts( message.cast(), msg_length, )) }; cb(msg_source, msg_type, id, msg_severity, message); } }); if res.is_err() { // eprintln is not used since it can panic and unwinds are unwanted because // unwinding from Rust code into foreign code is UB. stderr() .write_all(b"ERROR: Panic in debug message callback") .ok(); println!(); } }