From da329909f93597970afd169cee28ece3bee7127b Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 15 Oct 2023 00:06:07 +0200 Subject: feat(engine): add logging OpenGL debug messages --- Cargo.lock | 38 ++++++++++++ engine/Cargo.toml | 4 ++ engine/src/lib.rs | 8 ++- engine/src/opengl/debug.rs | 145 +++++++++++++++++++++++++++++++++++++++++++++ engine/src/opengl/mod.rs | 5 ++ engine/src/opengl/util.rs | 30 ++++++++++ engine/src/renderer/mod.rs | 49 +++++++++++++++ 7 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 engine/src/opengl/debug.rs create mode 100644 engine/src/opengl/util.rs diff --git a/Cargo.lock b/Cargo.lock index 13b0fcb..461fbd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,7 @@ dependencies = [ "gl", "glfw", "thiserror", + "tracing", ] [[package]] @@ -243,6 +244,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + [[package]] name = "prettyplease" version = "0.2.15" @@ -356,6 +363,37 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 40b2e9f..ff52cce 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -3,8 +3,12 @@ name = "engine" version = "0.1.0" edition = "2021" +[features] +debug = ["dep:tracing"] + [dependencies] glfw = { path = "../glfw" } thiserror = "1.0.49" gl = "0.14.0" bitflags = "2.4.0" +tracing = { version = "0.1.39", optional = true } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 407df32..dc76b37 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -29,7 +29,13 @@ impl Engine /// Will return `Err` if window creation or window configuration fails. pub fn new(window_size: &WindowSize, window_title: &str) -> Result { - let window = WindowBuilder::new() + let window_builder = WindowBuilder::new(); + + #[cfg(feature = "debug")] + let window_builder = + window_builder.hint(glfw::window::Hint::OpenGLDebugContext, 1); + + let window = window_builder .create(window_size, window_title) .map_err(Error::CreateWindowFailed)?; diff --git a/engine/src/opengl/debug.rs b/engine/src/opengl/debug.rs new file mode 100644 index 0000000..203590a --- /dev/null +++ b/engine/src/opengl/debug.rs @@ -0,0 +1,145 @@ +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!(); + } +} diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index a58e72e..ce375f5 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -8,6 +8,11 @@ pub mod shader; pub mod vertex_array; pub mod vertex_buffer; +mod util; + +#[cfg(feature = "debug")] +pub mod debug; + pub fn set_viewport(position: &Vec2, size: &WindowSize) { unsafe { diff --git a/engine/src/opengl/util.rs b/engine/src/opengl/util.rs new file mode 100644 index 0000000..e60778f --- /dev/null +++ b/engine/src/opengl/util.rs @@ -0,0 +1,30 @@ +// May only be used when certain crate features are enabled +#![allow(unused_macros, unused_imports)] + +macro_rules! gl_enum { + ( + $visibility: vis enum $name: ident + {$( + $variant: ident = gl::$gl_enum: ident, + )+} + ) => { + #[derive(Debug, Clone, Copy)] + #[repr(u32)] + $visibility enum $name + {$( + $variant = gl::$gl_enum, + )+} + + impl $name { + fn from_gl(num: gl::types::GLenum) -> Option + { + match num { + $(gl::$gl_enum => Some(Self::$variant),)+ + _ => None + } + } + } + }; +} + +pub(crate) use gl_enum; diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index 4ed3c0a..cc1631f 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -3,6 +3,8 @@ use std::process::abort; use glfw::WindowSize; +#[cfg(feature = "debug")] +use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; use crate::opengl::shader::Program as ShaderProgram; use crate::opengl::vertex_array::{PrimitiveKind, VertexArray}; use crate::opengl::vertex_buffer::{BufferUsage, VertexBuffer}; @@ -28,6 +30,22 @@ pub fn initialize(window: &glfw::Window) -> Result<(), Error> } }); + #[cfg(feature = "debug")] + { + use crate::opengl::debug::{ + enable_debug_output, + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + }; + + enable_debug_output(); + + set_debug_message_callback(opengl_debug_message_cb); + + set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); + } + let window_size = window.size().map_err(Error::GetWindowSizeFailed)?; set_viewport(&Vec2 { x: 0, y: 0 }, &window_size); @@ -35,6 +53,37 @@ pub fn initialize(window: &glfw::Window) -> Result<(), Error> Ok(()) } +#[cfg(feature = "debug")] +#[tracing::instrument(skip_all)] +fn opengl_debug_message_cb( + source: MessageSource, + ty: MessageType, + id: u32, + severity: MessageSeverity, + message: &str, +) +{ + use tracing::{event, Level}; + + macro_rules! create_event { + ($level: expr) => { + event!($level, ?source, ?ty, id, ?severity, message); + }; + } + + match ty { + MessageType::Error => { + create_event!(Level::ERROR); + } + MessageType::Other => { + create_event!(Level::INFO); + } + _ => { + create_event!(Level::WARN); + } + }; +} + pub fn render<'renderable>(renderables: impl IntoIterator) { clear_buffers(BufferClearMask::COLOR); -- cgit v1.2.3-18-g5258