diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index f00625e..14538e0 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -12,6 +12,7 @@ input = { version = "0.5.0", features = ["udev"], optional = true } thiserror = "1" xcursor = { version = "0.3.3", optional = true } image = { version = "0.23.14", default-features = false, optional = true } +fps_ticker = { version = "1.0.0", optional = true } rand = "0.7" slog = { version = "2.1.1" } slog-term = "2.8" @@ -43,4 +44,5 @@ logind = [ "smithay/backend_session_logind" ] elogind = ["logind", "smithay/backend_session_elogind" ] libseat = ["smithay/backend_session_libseat" ] xwayland = [ "smithay/xwayland", "x11rb" ] -test_all_features = ["default"] +debug = [ "fps_ticker", "image/png" ] +test_all_features = ["default", "debug"] diff --git a/anvil/resources/numbers.png b/anvil/resources/numbers.png new file mode 100644 index 0000000..bc01532 Binary files /dev/null and b/anvil/resources/numbers.png differ diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 821aca7..2063a7c 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -2,7 +2,11 @@ use std::{cell::RefCell, sync::Mutex}; +#[cfg(feature = "image")] +use image::{ImageBuffer, Rgba}; use slog::Logger; +#[cfg(feature = "image")] +use smithay::backend::renderer::gles2::{Gles2Error, Gles2Renderer, Gles2Texture}; use smithay::{ backend::{ renderer::{buffer_type, BufferType, Frame, ImportAll, Renderer, Texture, Transform}, @@ -260,3 +264,80 @@ where } draw_surface_tree(renderer, frame, surface, location, output_scale, log) } + +#[cfg(feature = "debug")] +pub static FPS_NUMBERS_PNG: &[u8] = include_bytes!("../resources/numbers.png"); + +#[cfg(feature = "debug")] +pub fn draw_fps( + _renderer: &mut R, + frame: &mut F, + texture: &T, + output_scale: f64, + value: u32, +) -> Result<(), SwapBuffersError> +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error + Into, + T: Texture + 'static, +{ + let value_str = value.to_string(); + let mut offset_x = 0f64; + for digit in value_str.chars().map(|d| d.to_digit(10).unwrap()) { + frame + .render_texture_from_to( + texture, + match digit { + 9 => Rectangle::from_loc_and_size((0, 0), (22, 35)), + 6 => Rectangle::from_loc_and_size((22, 0), (22, 35)), + 3 => Rectangle::from_loc_and_size((44, 0), (22, 35)), + 1 => Rectangle::from_loc_and_size((66, 0), (22, 35)), + 8 => Rectangle::from_loc_and_size((0, 35), (22, 35)), + 0 => Rectangle::from_loc_and_size((22, 35), (22, 35)), + 2 => Rectangle::from_loc_and_size((44, 35), (22, 35)), + 7 => Rectangle::from_loc_and_size((0, 70), (22, 35)), + 4 => Rectangle::from_loc_and_size((22, 70), (22, 35)), + 5 => Rectangle::from_loc_and_size((44, 70), (22, 35)), + _ => unreachable!(), + }, + Rectangle::from_loc_and_size((offset_x, 0.0), (22.0 * output_scale, 35.0 * output_scale)), + Transform::Normal, + 1.0, + ) + .map_err(Into::into)?; + offset_x += 24.0 * output_scale; + } + + Ok(()) +} + +#[cfg(feature = "image")] +pub fn import_bitmap>( + renderer: &mut Gles2Renderer, + image: &ImageBuffer, C>, +) -> Result { + use smithay::backend::renderer::gles2::ffi; + + renderer.with_context(|renderer, gl| unsafe { + let mut tex = 0; + gl.GenTextures(1, &mut tex); + gl.BindTexture(ffi::TEXTURE_2D, tex); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); + gl.TexImage2D( + ffi::TEXTURE_2D, + 0, + ffi::RGBA as i32, + image.width() as i32, + image.height() as i32, + 0, + ffi::RGBA, + ffi::UNSIGNED_BYTE as u32, + image.as_ptr() as *const _, + ); + gl.BindTexture(ffi::TEXTURE_2D, 0); + + Gles2Texture::from_raw(renderer, tex, image.width(), image.height()) + }) +} diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index ead4f40..d1ad5d6 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -9,7 +9,7 @@ use std::{ time::Duration, }; -use image::{ImageBuffer, Rgba}; +use image::ImageBuffer; use slog::Logger; use smithay::{ @@ -19,7 +19,7 @@ use smithay::{ egl::{EGLContext, EGLDisplay}, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ - gles2::{Gles2Error, Gles2Renderer, Gles2Texture}, + gles2::{Gles2Renderer, Gles2Texture}, Bind, Frame, Renderer, Transform, }, session::{auto::AutoSession, Session, Signal as SessionSignal}, @@ -254,10 +254,18 @@ pub fn run_udev( pub type RenderSurface = GbmBufferedSurface; +struct SurfaceData { + surface: RenderSurface, + #[cfg(feature = "debug")] + fps: fps_ticker::Fps, +} + struct BackendData { _restart_token: SignalToken, - surfaces: Rc>>>>, + surfaces: Rc>>>>, pointer_images: Vec<(xcursor::parser::Image, Gles2Texture)>, + #[cfg(feature = "debug")] + fps_texture: Gles2Texture, renderer: Rc>, gbm: GbmDevice, registration_token: RegistrationToken, @@ -272,7 +280,7 @@ fn scan_connectors( output_map: &mut crate::output_map::OutputMap, signaler: &Signaler, logger: &::slog::Logger, -) -> HashMap>> { +) -> HashMap>> { // Get a set of all modesetting resource handles (excluding planes): let res_handles = device.resource_handles().unwrap(); @@ -321,7 +329,7 @@ fn scan_connectors( let renderer_formats = Bind::::supported_formats(renderer).expect("Dmabuf renderer without formats"); - let renderer = + let gbm_surface = match GbmBufferedSurface::new(surface, gbm.clone(), renderer_formats, logger.clone()) { Ok(renderer) => renderer, @@ -373,7 +381,11 @@ fn scan_connectors( device_id: device.device_id(), }); - entry.insert(Rc::new(RefCell::new(renderer))); + entry.insert(Rc::new(RefCell::new(SurfaceData { + surface: gbm_surface, + #[cfg(feature = "debug")] + fps: fps_ticker::Fps::default(), + }))); break 'outer; } } @@ -496,6 +508,19 @@ impl AnvilState { schedule_initial_render(backend.clone(), renderer.clone(), &self.handle, self.log.clone()); } + #[cfg(feature = "debug")] + let fps_texture = import_bitmap( + &mut renderer.borrow_mut(), + &image::io::Reader::with_format( + std::io::Cursor::new(FPS_NUMBERS_PNG), + image::ImageFormat::Png, + ) + .decode() + .unwrap() + .to_rgba8(), + ) + .expect("Unable to upload FPS texture"); + self.backend_data.backends.insert( dev_id, BackendData { @@ -506,6 +531,8 @@ impl AnvilState { renderer, gbm, pointer_images: Vec::new(), + #[cfg(feature = "debug")] + fps_texture, dev_id, }, ); @@ -596,7 +623,7 @@ impl AnvilState { .iter() .flat_map(|crtc| surfaces.get(&crtc).map(|surface| (crtc, surface))); - let to_render_iter: &mut dyn Iterator>)> = + let to_render_iter: &mut dyn Iterator>)> = if crtc.is_some() { &mut option_iter } else { @@ -632,6 +659,8 @@ impl AnvilState { &*self.output_map.borrow(), self.pointer_location, &pointer_image, + #[cfg(feature = "debug")] + &device_backend.fps_texture, &*self.dnd_icon.lock().unwrap(), &mut *self.cursor_status.lock().unwrap(), &self.log, @@ -671,7 +700,7 @@ impl AnvilState { #[allow(clippy::too_many_arguments)] fn render_surface( - surface: &mut RenderSurface, + surface: &mut SurfaceData, renderer: &mut Gles2Renderer, device_id: dev_t, crtc: crtc::Handle, @@ -679,11 +708,12 @@ fn render_surface( output_map: &crate::output_map::OutputMap, pointer_location: Point, pointer_image: &Gles2Texture, + #[cfg(feature = "debug")] fps_texture: &Gles2Texture, dnd_icon: &Option, cursor_status: &mut CursorImageStatus, logger: &slog::Logger, ) -> Result<(), SwapBuffersError> { - surface.frame_submitted()?; + surface.surface.frame_submitted()?; let output = output_map .find(|o| o.userdata().get::() == Some(&UdevOutputId { device_id, crtc })) @@ -696,7 +726,7 @@ fn render_surface( return Ok(()); }; - let dmabuf = surface.next_buffer()?; + let dmabuf = surface.surface.next_buffer()?; renderer.bind(dmabuf)?; // and draw to our buffer match renderer @@ -763,6 +793,18 @@ fn render_surface( } } } + + #[cfg(feature = "debug")] + { + draw_fps( + renderer, + frame, + fps_texture, + output_scale as f64, + surface.fps.avg().round() as u32, + )?; + surface.fps.tick(); + } Ok(()) }, ) @@ -770,13 +812,16 @@ fn render_surface( .and_then(|x| x) .map_err(Into::::into) { - Ok(()) => surface.queue_buffer().map_err(Into::::into), + Ok(()) => surface + .surface + .queue_buffer() + .map_err(Into::::into), Err(err) => Err(err), } } fn schedule_initial_render( - surface: Rc>, + surface: Rc>, renderer: Rc>, evt_handle: &LoopHandle<'static, Data>, logger: ::slog::Logger, @@ -784,7 +829,7 @@ fn schedule_initial_render( let result = { let mut surface = surface.borrow_mut(); let mut renderer = renderer.borrow_mut(); - initial_render(&mut *surface, &mut *renderer) + initial_render(&mut surface.surface, &mut *renderer) }; if let Err(err) = result { match err { @@ -815,32 +860,3 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> surface.queue_buffer()?; Ok(()) } - -fn import_bitmap>( - renderer: &mut Gles2Renderer, - image: &ImageBuffer, C>, -) -> Result { - use smithay::backend::renderer::gles2::ffi; - - renderer.with_context(|renderer, gl| unsafe { - let mut tex = 0; - gl.GenTextures(1, &mut tex); - gl.BindTexture(ffi::TEXTURE_2D, tex); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); - gl.TexImage2D( - ffi::TEXTURE_2D, - 0, - ffi::RGBA as i32, - image.width() as i32, - image.height() as i32, - 0, - ffi::RGBA, - ffi::UNSIGNED_BYTE as u32, - image.as_ptr() as *const _, - ); - gl.BindTexture(ffi::TEXTURE_2D, 0); - - Gles2Texture::from_raw(renderer, tex, image.width(), image.height()) - }) -} diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 589f1a5..dfe38a7 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -1,5 +1,7 @@ use std::{cell::RefCell, rc::Rc, sync::atomic::Ordering, time::Duration}; +#[cfg(feature = "debug")] +use smithay::backend::renderer::gles2::Gles2Texture; #[cfg(feature = "egl")] use smithay::{ backend::renderer::{ImportDma, ImportEgl}, @@ -24,7 +26,12 @@ use crate::state::{AnvilState, Backend}; pub const OUTPUT_NAME: &str = "winit"; -pub struct WinitData; +pub struct WinitData { + #[cfg(feature = "debug")] + fps_texture: Gles2Texture, + #[cfg(feature = "debug")] + pub fps: fps_ticker::Fps, +} impl Backend for WinitData { fn seat_name(&self) -> String { @@ -71,7 +78,20 @@ pub fn run_winit( * Initialize the globals */ - let mut state = AnvilState::init(display.clone(), event_loop.handle(), WinitData, log.clone()); + let data = WinitData { + #[cfg(feature = "debug")] + fps_texture: import_bitmap( + &mut renderer.borrow_mut().renderer(), + &image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png) + .decode() + .unwrap() + .to_rgba8(), + ) + .expect("Unable to upload FPS texture"), + #[cfg(feature = "debug")] + fps: fps_ticker::Fps::default(), + }; + let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone()); let mode = Mode { size, @@ -176,6 +196,18 @@ pub fn run_winit( } } + #[cfg(feature = "debug")] + { + let fps = state.backend_data.fps.avg().round() as u32; + draw_fps( + renderer, + frame, + &state.backend_data.fps_texture, + output_scale as f64, + fps, + )?; + } + Ok(()) }) .map_err(Into::::into) @@ -206,6 +238,9 @@ pub fn run_winit( state.window_map.borrow_mut().refresh(); state.output_map.borrow_mut().refresh(); } + + #[cfg(feature = "debug")] + state.backend_data.fps.tick(); } // Cleanup stuff