diff --git a/Cargo.toml b/Cargo.toml index 5e6c051..78d3cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ drm = { version = "0.4.0", optional = true } drm-ffi = { version = "0.1.0", optional = true } gbm = { version = "0.6.0", optional = true, default-features = false, features = ["drm-support"] } input = { version = "0.6", default-features = false, features=["libinput_1_14"], optional = true } -image = { version = "0.23.14", default-features = false, optional = true } lazy_static = "1" libc = "0.2.70" libseat= { version = "0.1.1", optional = true } @@ -50,7 +49,7 @@ gl_generator = { version = "0.14", optional = true } pkg-config = { version = "0.3.17", optional = true } [features] -default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "image", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog"] +default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"] backend_drm = ["drm", "drm-ffi"] backend_gbm = ["gbm"] diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index d321b5b..14538e0 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -9,7 +9,10 @@ edition = "2018" [dependencies] bitflags = "1.2.1" input = { version = "0.5.0", features = ["udev"], optional = true } -image = { version = "0.23.0", optional = true, default-features = false } +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" @@ -36,9 +39,10 @@ gl_generator = "0.14" default = [ "winit", "udev", "logind", "egl", "xwayland" ] egl = [ "smithay/use_system_lib", "smithay/backend_egl" ] winit = [ "smithay/backend_winit" ] -udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm", "smithay/backend_gbm", "smithay/backend_egl", "smithay/backend_session", "input", "image", "smithay/image"] +udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm", "smithay/backend_gbm", "smithay/backend_egl", "smithay/backend_session", "input", "image", "xcursor" ] 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/cursor.rgba b/anvil/resources/cursor.rgba index 1bd3509..729c1cc 100644 Binary files a/anvil/resources/cursor.rgba and b/anvil/resources/cursor.rgba differ diff --git a/anvil/resources/cursor2.rgba b/anvil/resources/cursor2.rgba deleted file mode 100644 index 729c1cc..0000000 Binary files a/anvil/resources/cursor2.rgba and /dev/null differ 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/cursor.rs b/anvil/src/cursor.rs new file mode 100644 index 0000000..b5b5e12 --- /dev/null +++ b/anvil/src/cursor.rs @@ -0,0 +1,92 @@ +use std::io::Read; + +use xcursor::{ + parser::{parse_xcursor, Image}, + CursorTheme, +}; + +static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba"); + +pub struct Cursor { + icons: Vec, + size: u32, +} + +impl Cursor { + pub fn load(log: &::slog::Logger) -> Cursor { + let name = std::env::var("XCURSOR_THEME") + .ok() + .unwrap_or_else(|| "default".into()); + let size = std::env::var("XCURSOR_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(24); + + let theme = CursorTheme::load(&name); + let icons = load_icon(&theme) + .map_err(|err| slog::warn!(log, "Unable to load xcursor: {}, using fallback cursor", err)) + .unwrap_or_else(|_| { + vec![Image { + size: 32, + width: 64, + height: 64, + xhot: 1, + yhot: 1, + delay: 1, + pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), + pixels_argb: vec![], //unused + }] + }); + + Cursor { icons, size } + } + + pub fn get_image(&self, scale: u32, millis: u32) -> Image { + let size = self.size * scale; + frame(millis, size, &self.icons) + } +} + +fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { + // Follow the nominal size of the cursor to choose the nearest + let nearest_image = images + .iter() + .min_by_key(|image| (size as i32 - image.size as i32).abs()) + .unwrap(); + + images + .iter() + .filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height) +} + +fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { + let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); + millis %= total; + + for img in nearest_images(size, images) { + if millis < img.delay { + return img.clone(); + } + millis -= img.delay; + } + + unreachable!() +} + +#[derive(thiserror::Error, Debug)] +enum Error { + #[error("Theme has no default cursor")] + NoDefaultCursor, + #[error("Error opening xcursor file: {0}")] + File(#[from] std::io::Error), + #[error("Failed to parse XCursor file")] + Parse, +} + +fn load_icon(theme: &CursorTheme) -> Result, Error> { + let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?; + let mut cursor_file = std::fs::File::open(&icon_path)?; + let mut cursor_data = Vec::new(); + cursor_file.read_to_end(&mut cursor_data)?; + parse_xcursor(&cursor_data).ok_or(Error::Parse) +} diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 913c3c6..dc33336 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}, @@ -155,7 +159,7 @@ where let mut location = *location; if let Some(ref data) = states.data_map.get::>() { let mut data = data.borrow_mut(); - let buffer_scale = data.buffer_scale as f32; + let buffer_scale = data.buffer_scale; if let Some(texture) = data .texture .as_mut() @@ -167,12 +171,12 @@ where let current = states.cached_state.current::(); location += current.location; } - let render_scale = output_scale as f32 / buffer_scale; if let Err(err) = frame.render_texture_at( &texture.texture, location.to_f64().to_physical(output_scale as f64).to_i32_round(), + buffer_scale, + output_scale as f64, Transform::Normal, /* TODO */ - render_scale, 1.0, ) { result = Err(err.into()); @@ -260,3 +264,84 @@ 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() as i32, image.height() as i32).into(), + ) + }) +} diff --git a/anvil/src/main.rs b/anvil/src/main.rs index 46183ca..34f1e88 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -14,6 +14,8 @@ use std::{cell::RefCell, rc::Rc}; use slog::Drain; use smithay::reexports::{calloop::EventLoop, wayland_server::Display}; +#[cfg(feature = "udev")] +mod cursor; mod drawing; mod input_handler; mod shell; diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index f95473c..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::{ @@ -90,7 +90,7 @@ pub struct UdevData { primary_gpu: Option, backends: HashMap, signaler: Signaler, - pointer_image: ImageBuffer, Vec>, + pointer_image: crate::cursor::Cursor, render_timer: TimerHandle<(u64, crtc::Handle)>, } @@ -122,7 +122,6 @@ pub fn run_udev( /* * Initialize the compositor */ - let pointer_bytes = include_bytes!("../resources/cursor2.rgba"); #[cfg(feature = "egl")] let primary_gpu = primary_gpu(&session.seat()).unwrap_or_default(); @@ -135,7 +134,7 @@ pub fn run_udev( primary_gpu, backends: HashMap::new(), signaler: session_signal.clone(), - pointer_image: ImageBuffer::from_raw(64, 64, pointer_bytes.to_vec()).unwrap(), + pointer_image: crate::cursor::Cursor::load(&log), render_timer: timer.handle(), }; let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone()); @@ -255,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>>>>, - pointer_image: Gles2Texture, + surfaces: Rc>>>>, + pointer_images: Vec<(xcursor::parser::Image, Gles2Texture)>, + #[cfg(feature = "debug")] + fps_texture: Gles2Texture, renderer: Rc>, gbm: GbmDevice, registration_token: RegistrationToken, @@ -273,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(); @@ -322,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, @@ -374,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; } } @@ -469,11 +480,6 @@ impl AnvilState { &self.log, ))); - let pointer_image = renderer - .borrow_mut() - .import_bitmap(&self.backend_data.pointer_image) - .expect("Failed to load pointer"); - let dev_id = device.device_id(); let handle = self.handle.clone(); let restart_token = self.backend_data.signaler.register(move |signal| match signal { @@ -502,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 { @@ -511,7 +530,9 @@ impl AnvilState { surfaces: backends, renderer, gbm, - pointer_image, + pointer_images: Vec::new(), + #[cfg(feature = "debug")] + fps_texture, dev_id, }, ); @@ -602,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 { @@ -610,15 +631,36 @@ impl AnvilState { }; for (&crtc, surface) in to_render_iter { + // TODO get scale from the rendersurface when supporting HiDPI + let frame = self + .backend_data + .pointer_image + .get_image(1 /*scale*/, self.start_time.elapsed().as_millis() as u32); + let renderer = &mut *device_backend.renderer.borrow_mut(); + let pointer_images = &mut device_backend.pointer_images; + let pointer_image = pointer_images + .iter() + .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) + .cloned() + .unwrap_or_else(|| { + let image = + ImageBuffer::from_raw(frame.width, frame.height, &*frame.pixels_rgba).unwrap(); + let texture = import_bitmap(renderer, &image).expect("Failed to import cursor bitmap"); + pointer_images.push((frame, texture.clone())); + texture + }); + let result = render_surface( &mut *surface.borrow_mut(), - &mut *device_backend.renderer.borrow_mut(), + renderer, device_backend.dev_id, crtc, &mut *self.window_map.borrow_mut(), &*self.output_map.borrow(), self.pointer_location, - &device_backend.pointer_image, + &pointer_image, + #[cfg(feature = "debug")] + &device_backend.fps_texture, &*self.dnd_icon.lock().unwrap(), &mut *self.cursor_status.lock().unwrap(), &self.log, @@ -658,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, @@ -666,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 })) @@ -683,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 @@ -742,13 +785,26 @@ fn render_surface( .to_f64() .to_physical(output_scale as f64) .to_i32_round(), + 1, + output_scale as f64, Transform::Normal, - output_scale, 1.0, )?; } } } + + #[cfg(feature = "debug")] + { + draw_fps( + renderer, + frame, + fps_texture, + output_scale as f64, + surface.fps.avg().round() as u32, + )?; + surface.fps.tick(); + } Ok(()) }, ) @@ -756,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, @@ -770,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 { 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 diff --git a/src/backend/allocator/dmabuf.rs b/src/backend/allocator/dmabuf.rs index df2e119..090d160 100644 --- a/src/backend/allocator/dmabuf.rs +++ b/src/backend/allocator/dmabuf.rs @@ -11,6 +11,7 @@ //! the lifetime of the buffer. E.g. when you are only caching associated resources for a dmabuf. use super::{Buffer, Format, Fourcc, Modifier}; +use crate::utils::{Buffer as BufferCoords, Size}; use std::hash::{Hash, Hasher}; use std::os::unix::io::{IntoRawFd, RawFd}; use std::sync::{Arc, Weak}; @@ -22,10 +23,8 @@ pub const MAX_PLANES: usize = 4; pub(crate) struct DmabufInternal { /// The submitted planes pub planes: Vec, - /// The width of this buffer - pub width: i32, - /// The height of this buffer - pub height: i32, + /// The size of this buffer + pub size: Size, /// The format in use pub format: Fourcc, /// The flags applied to it @@ -107,12 +106,8 @@ impl Hash for WeakDmabuf { } impl Buffer for Dmabuf { - fn width(&self) -> u32 { - self.0.width as u32 - } - - fn height(&self) -> u32 { - self.0.height as u32 + fn size(&self) -> Size { + self.0.size } fn format(&self) -> Format { @@ -172,8 +167,7 @@ impl Dmabuf { DmabufBuilder { internal: DmabufInternal { planes: Vec::with_capacity(MAX_PLANES), - width: src.width() as i32, - height: src.height() as i32, + size: src.size(), format: src.format().code, flags, }, @@ -181,12 +175,15 @@ impl Dmabuf { } /// Create a new Dmabuf builder - pub fn builder(width: u32, height: u32, format: Fourcc, flags: DmabufFlags) -> DmabufBuilder { + pub fn builder( + size: impl Into>, + format: Fourcc, + flags: DmabufFlags, + ) -> DmabufBuilder { DmabufBuilder { internal: DmabufInternal { planes: Vec::with_capacity(MAX_PLANES), - width: width as i32, - height: height as i32, + size: size.into(), format, flags, }, diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs index 0ee8632..fab167a 100644 --- a/src/backend/allocator/dumb.rs +++ b/src/backend/allocator/dumb.rs @@ -9,6 +9,7 @@ use drm::control::{dumbbuffer::DumbBuffer as Handle, Device as ControlDevice}; use super::{Allocator, Buffer, Format, Fourcc, Modifier}; use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal, FdWrapper}; +use crate::utils::{Buffer as BufferCoords, Size}; /// Wrapper around raw DumbBuffer handles. pub struct DumbBuffer { @@ -61,12 +62,9 @@ impl Allocator> for DrmDevice { } impl Buffer for DumbBuffer { - fn width(&self) -> u32 { - self.handle.size().0 - } - - fn height(&self) -> u32 { - self.handle.size().1 + fn size(&self) -> Size { + let (w, h) = self.handle.size(); + (w as i32, h as i32).into() } fn format(&self) -> Format { diff --git a/src/backend/allocator/gbm.rs b/src/backend/allocator/gbm.rs index 45b6ca2..f5bf9fd 100644 --- a/src/backend/allocator/gbm.rs +++ b/src/backend/allocator/gbm.rs @@ -8,6 +8,7 @@ use super::{ dmabuf::{AsDmabuf, Dmabuf, DmabufFlags, MAX_PLANES}, Allocator, Buffer, Format, Fourcc, Modifier, }; +use crate::utils::{Buffer as BufferCoords, Size}; pub use gbm::{BufferObject as GbmBuffer, BufferObjectFlags as GbmBufferFlags, Device as GbmDevice}; use std::os::unix::io::AsRawFd; @@ -39,12 +40,12 @@ impl Allocator> for GbmDevice { } impl Buffer for GbmBuffer { - fn width(&self) -> u32 { - self.width().unwrap_or(0) - } - - fn height(&self) -> u32 { - self.height().unwrap_or(0) + fn size(&self) -> Size { + ( + self.width().unwrap_or(0) as i32, + self.height().unwrap_or(0) as i32, + ) + .into() } fn format(&self) -> Format { diff --git a/src/backend/allocator/mod.rs b/src/backend/allocator/mod.rs index e9865b3..108f0bf 100644 --- a/src/backend/allocator/mod.rs +++ b/src/backend/allocator/mod.rs @@ -22,6 +22,7 @@ pub mod dumb; pub mod gbm; mod swapchain; +use crate::utils::{Buffer as BufferCoords, Size}; pub use swapchain::{Slot, Swapchain}; pub use drm_fourcc::{ @@ -32,13 +33,15 @@ pub use drm_fourcc::{ /// Common trait describing common properties of most types of buffers. pub trait Buffer { /// Width of the two-dimensional buffer - fn width(&self) -> u32; - /// Height of the two-dimensional buffer - fn height(&self) -> u32; - /// Size (w x h) of the two-dimensional buffer - fn size(&self) -> (u32, u32) { - (self.width(), self.height()) + fn width(&self) -> u32 { + self.size().w as u32 } + /// Height of the two-dimensional buffer + fn height(&self) -> u32 { + self.size().h as u32 + } + /// Size of the two-dimensional buffer + fn size(&self) -> Size; /// Pixel format of the buffer fn format(&self) -> Format; } diff --git a/src/backend/egl/display.rs b/src/backend/egl/display.rs index 8a7da5a..98542a8 100644 --- a/src/backend/egl/display.rs +++ b/src/backend/egl/display.rs @@ -808,8 +808,7 @@ impl EGLBufferReader { Ok(EGLBuffer { display: self.display.clone(), - width: width as u32, - height: height as u32, + size: (width, height).into(), // y_inverted is negated here because the gles2 renderer // already inverts the buffer during rendering. y_inverted: !y_inverted, diff --git a/src/backend/egl/mod.rs b/src/backend/egl/mod.rs index 2cd26b3..746cc67 100644 --- a/src/backend/egl/mod.rs +++ b/src/backend/egl/mod.rs @@ -32,6 +32,8 @@ pub use self::context::EGLContext; mod error; pub use self::error::*; use crate::backend::SwapBuffersError as GraphicsSwapBuffersError; +#[cfg(feature = "wayland_frontend")] +use crate::utils::{Buffer, Size}; use nix::libc::c_void; @@ -248,10 +250,8 @@ impl Format { #[derive(Debug)] pub struct EGLBuffer { display: Arc, - /// Width in pixels - pub width: u32, - /// Height in pixels - pub height: u32, + /// Size of the buffer + pub size: Size, /// If the y-axis is inverted or not pub y_inverted: bool, /// Format of these images diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 9b3fbf8..9724b28 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -11,7 +11,7 @@ use std::sync::{ }; use std::{collections::HashSet, os::raw::c_char}; -use cgmath::{prelude::*, Matrix3}; +use cgmath::{prelude::*, Matrix3, Vector2}; mod shaders; mod version; @@ -34,7 +34,6 @@ use super::ImportEgl; use super::{ImportDma, ImportShm}; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat}; -#[cfg(feature = "wayland_frontend")] use crate::utils::{Buffer, Rectangle}; #[cfg(feature = "wayland_frontend")] use wayland_server::protocol::{wl_buffer, wl_shm}; @@ -67,14 +66,42 @@ struct Gles2Program { #[derive(Debug, Clone)] pub struct Gles2Texture(Rc); +impl Gles2Texture { + /// Create a Gles2Texture from a raw gl texture id. + /// + /// This expects the texture to be in RGBA format to be rendered + /// correctly by the `render_texture*`-functions of [`Frame`](super::Frame). + /// It is also expected to not be external or y_inverted. + /// + /// Ownership over the texture is taken by the renderer, you should not free the texture yourself. + /// + /// # Safety + /// + /// The renderer cannot make sure `tex` is a valid texture id. + pub unsafe fn from_raw( + renderer: &Gles2Renderer, + tex: ffi::types::GLuint, + size: Size, + ) -> Gles2Texture { + Gles2Texture(Rc::new(Gles2TextureInternal { + texture: tex, + texture_kind: 0, + is_external: false, + y_inverted: false, + size, + egl_images: None, + destruction_callback_sender: renderer.destruction_callback_sender.clone(), + })) + } +} + #[derive(Debug)] struct Gles2TextureInternal { texture: ffi::types::GLuint, texture_kind: usize, is_external: bool, y_inverted: bool, - width: u32, - height: u32, + size: Size, egl_images: Option>, destruction_callback_sender: Sender, } @@ -101,10 +128,13 @@ enum CleanupResource { impl Texture for Gles2Texture { fn width(&self) -> u32 { - self.0.width + self.0.size.w as u32 } fn height(&self) -> u32 { - self.0.height + self.0.size.h as u32 + } + fn size(&self) -> Size { + self.0.size } } @@ -559,8 +589,7 @@ impl ImportShm for Gles2Renderer { texture_kind: shader_idx, is_external: false, y_inverted: false, - width: width as u32, - height: height as u32, + size: (width, height).into(), egl_images: None, destruction_callback_sender: self.destruction_callback_sender.clone(), }) @@ -689,8 +718,7 @@ impl ImportEgl for Gles2Renderer { }, is_external: egl.format == EGLFormat::External, y_inverted: egl.y_inverted, - width: egl.width, - height: egl.height, + size: egl.size, egl_images: Some(egl.into_images()), destruction_callback_sender: self.destruction_callback_sender.clone(), })); @@ -723,8 +751,7 @@ impl ImportDma for Gles2Renderer { texture_kind: if is_external { 2 } else { 0 }, is_external, y_inverted: buffer.y_inverted(), - width: buffer.width(), - height: buffer.height(), + size: buffer.size(), egl_images: Some(vec![image]), destruction_callback_sender: self.destruction_callback_sender.clone(), })); @@ -950,69 +977,28 @@ impl Drop for Gles2Renderer { } } -static VERTS: [ffi::types::GLfloat; 8] = [ - 1.0, 0.0, // top right - 0.0, 0.0, // top left - 1.0, 1.0, // bottom right - 0.0, 1.0, // bottom left -]; - -static TEX_COORDS: [ffi::types::GLfloat; 8] = [ - 1.0, 0.0, // top right - 0.0, 0.0, // top left - 1.0, 1.0, // bottom right - 0.0, 1.0, // bottom left -]; +impl Gles2Renderer { + /// Run custom code in the GL context owned by this renderer. + /// + /// *Note*: Any changes to the GL state should be restored at the end of this function. + /// Otherwise this can lead to rendering errors while using functions of this renderer. + /// Relying on any state set by the renderer may break on any smithay update as the + /// details about how this renderer works are considered an implementation detail. + pub fn with_context(&mut self, func: F) -> Result + where + F: FnOnce(&mut Self, &ffi::Gles2) -> R, + { + self.make_current()?; + let gl = self.gl.clone(); + Ok(func(self, &gl)) + } +} impl Renderer for Gles2Renderer { type Error = Gles2Error; type TextureId = Gles2Texture; type Frame = Gles2Frame; - #[cfg(feature = "image")] - fn import_bitmap>( - &mut self, - image: &image::ImageBuffer, C>, - ) -> Result { - self.make_current()?; - - let mut tex = 0; - unsafe { - self.gl.GenTextures(1, &mut tex); - self.gl.BindTexture(ffi::TEXTURE_2D, tex); - self.gl - .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); - self.gl - .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); - self.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 _, - ); - self.gl.BindTexture(ffi::TEXTURE_2D, 0); - } - - let texture = Gles2Texture(Rc::new(Gles2TextureInternal { - texture: tex, - texture_kind: 0, - is_external: false, - y_inverted: false, - width: image.width(), - height: image.height(), - egl_images: None, - destruction_callback_sender: self.destruction_callback_sender.clone(), - })); - self.egl.unbind()?; - - Ok(texture) - } - fn render( &mut self, size: Size, @@ -1029,6 +1015,9 @@ impl Renderer for Gles2Renderer { unsafe { self.gl.Viewport(0, 0, size.w, size.h); + self.gl.Scissor(0, 0, size.w, size.h); + self.gl.Enable(ffi::SCISSOR_TEST); + self.gl.Enable(ffi::BLEND); self.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA); } @@ -1083,6 +1072,13 @@ impl Renderer for Gles2Renderer { } } +static VERTS: [ffi::types::GLfloat; 8] = [ + 1.0, 0.0, // top right + 0.0, 0.0, // top left + 1.0, 1.0, // bottom right + 0.0, 1.0, // bottom left +]; + impl Frame for Gles2Frame { type Error = Gles2Error; type TextureId = Gles2Texture; @@ -1100,6 +1096,7 @@ impl Frame for Gles2Frame { &mut self, tex: &Self::TextureId, mut matrix: Matrix3, + tex_coords: [Vector2; 4], alpha: f32, ) -> Result<(), Self::Error> { //apply output transformation @@ -1148,7 +1145,7 @@ impl Frame for Gles2Frame { ffi::FLOAT, ffi::FALSE, 0, - TEX_COORDS.as_ptr() as *const _, + tex_coords.as_ptr() as *const _, // cgmath::Vector2 is marked as repr(C), this cast should be safe ); self.gl diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 96b78ca..dffc150 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -10,16 +10,18 @@ use std::collections::HashSet; use std::error::Error; -use crate::utils::{Physical, Point, Size}; +use crate::utils::{Buffer, Physical, Point, Rectangle, Size}; #[cfg(feature = "wayland_frontend")] -use crate::{utils::Rectangle, wayland::compositor::SurfaceData}; -use cgmath::{prelude::*, Matrix3, Vector2}; +use crate::wayland::compositor::SurfaceData; +use cgmath::{prelude::*, Matrix3, Vector2, Vector3}; #[cfg(feature = "wayland_frontend")] use wayland_server::protocol::{wl_buffer, wl_shm}; #[cfg(feature = "renderer_gl")] pub mod gles2; +#[cfg(feature = "wayland_frontend")] +use crate::backend::allocator::{dmabuf::Dmabuf, Format}; #[cfg(all( feature = "wayland_frontend", feature = "backend_egl", @@ -29,11 +31,6 @@ use crate::backend::egl::{ display::{EGLBufferReader, BUFFER_READER}, Error as EglError, }; -#[cfg(feature = "wayland_frontend")] -use crate::{ - backend::allocator::{dmabuf::Dmabuf, Format}, - utils::Buffer, -}; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] /// Possible transformations to two-dimensional planes @@ -142,9 +139,9 @@ pub trait Unbind: Renderer { /// A two dimensional texture pub trait Texture { - /// Size of the texture plane (w x h) - fn size(&self) -> (u32, u32) { - (self.width(), self.height()) + /// Size of the texture plane + fn size(&self) -> Size { + Size::from((self.width() as i32, self.height() as i32)) } /// Width of the texture plane @@ -166,34 +163,57 @@ pub trait Frame { /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>; /// Render a texture to the current target using given projection matrix and alpha. - /// - /// This operation is only valid in between a `begin` and `finish`-call. - /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. + /// The given verticies are used to source the texture. This is mostly useful for cropping the texture. fn render_texture( &mut self, texture: &Self::TextureId, matrix: Matrix3, + tex_coords: [Vector2; 4], alpha: f32, ) -> Result<(), Self::Error>; + /// Render a texture to the current target as a flat 2d-plane at a given - /// position, applying the given transformation with the given alpha value. - /// - /// This operation is only valid in between a `begin` and `finish`-call. - /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. + /// position and applying the given transformation with the given alpha value. fn render_texture_at( &mut self, texture: &Self::TextureId, - pos: Point, + pos: Point, + texture_scale: i32, + output_scale: f64, + transform: Transform, + alpha: f32, + ) -> Result<(), Self::Error> { + self.render_texture_from_to( + texture, + Rectangle::from_loc_and_size(Point::::from((0, 0)), texture.size()), + Rectangle::from_loc_and_size( + pos, + texture + .size() + .to_logical(texture_scale) + .to_f64() + .to_physical(output_scale), + ), + transform, + alpha, + ) + } + + /// Render part of a texture as given by src to the current target into the rectangle described by dest + /// as a flat 2d-plane after applying the given transformations. + fn render_texture_from_to( + &mut self, + texture: &Self::TextureId, + src: Rectangle, + dest: Rectangle, transform: Transform, - scale: f32, alpha: f32, ) -> Result<(), Self::Error> { let mut mat = Matrix3::::identity(); // position and scale - let size = texture.size(); - mat = mat * Matrix3::from_translation(Vector2::new(pos.x as f32, pos.y as f32)); - mat = mat * Matrix3::from_nonuniform_scale(size.0 as f32 * scale, size.1 as f32 * scale); + mat = mat * Matrix3::from_translation(Vector2::new(dest.loc.x as f32, dest.loc.y as f32)); + mat = mat * Matrix3::from_nonuniform_scale(dest.size.w as f32, dest.size.h as f32); //apply surface transformation mat = mat * Matrix3::from_translation(Vector2::new(0.5, 0.5)); @@ -204,7 +224,24 @@ pub trait Frame { mat = mat * transform.invert().matrix(); mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); - self.render_texture(texture, mat, alpha) + // this matrix should be regular, we can expect invert to succeed + let tex_size = texture.size(); + let texture_mat = Matrix3::from_nonuniform_scale(tex_size.w as f32, tex_size.h as f32) + .invert() + .unwrap(); + let verts = [ + (texture_mat * Vector3::new((src.loc.x + src.size.w) as f32, src.loc.y as f32, 0.0)).truncate(), // top-right + (texture_mat * Vector3::new(src.loc.x as f32, src.loc.y as f32, 0.0)).truncate(), // top-left + (texture_mat + * Vector3::new( + (src.loc.x + src.size.w) as f32, + (src.loc.y + src.size.h) as f32, + 0.0, + )) + .truncate(), // bottom-right + (texture_mat * Vector3::new(src.loc.x as f32, (src.loc.y + src.size.h) as f32, 0.0)).truncate(), // bottom-left + ]; + self.render_texture(texture, mat, verts, alpha) } } @@ -217,20 +254,6 @@ pub trait Renderer { /// Type representing a currently in-progress frame during the [`Renderer::render`]-call type Frame: Frame; - /// Import a given bitmap into the renderer. - /// - /// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions. - /// - /// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it, - /// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak. - /// - /// This operation needs no bound or default rendering target. - #[cfg(feature = "image")] - fn import_bitmap>( - &mut self, - image: &image::ImageBuffer, C>, - ) -> Result; - /// Initialize a rendering context on the current rendering target with given dimensions and transformation. /// /// This function *may* error, if: diff --git a/src/wayland/dmabuf/mod.rs b/src/wayland/dmabuf/mod.rs index a572ba5..358f720 100644 --- a/src/wayland/dmabuf/mod.rs +++ b/src/wayland/dmabuf/mod.rs @@ -260,12 +260,7 @@ where return; } - let mut buf = Dmabuf::builder( - width as u32, - height as u32, - format, - DmabufFlags::from_bits_truncate(flags), - ); + let mut buf = Dmabuf::builder((width, height), format, DmabufFlags::from_bits_truncate(flags)); let planes = std::mem::take(&mut self.pending_planes); for (i, plane) in planes.into_iter().enumerate() { let offset = plane.offset; @@ -350,12 +345,7 @@ where return; } - let mut buf = Dmabuf::builder( - width as u32, - height as u32, - format, - DmabufFlags::from_bits_truncate(flags), - ); + let mut buf = Dmabuf::builder((width, height), format, DmabufFlags::from_bits_truncate(flags)); let planes = ::std::mem::take(&mut self.pending_planes); for (i, plane) in planes.into_iter().enumerate() { let offset = plane.offset;