From 3b8935cf55329d7e1e8fd89567298e34c0c47490 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 1 Jun 2022 21:46:31 +0200 Subject: [PATCH] all: implement hardware cursors --- jay-config/src/_private/client.rs | 7 + jay-config/src/_private/ipc.rs | 4 + jay-config/src/input.rs | 10 + src/backend.rs | 11 ++ src/backends/metal/video.rs | 307 ++++++++++++++++++++++++----- src/backends/x.rs | 1 + src/compositor.rs | 3 + src/config/handler.rs | 24 +++ src/cursor.rs | 83 +++++--- src/ifs/wl_seat.rs | 80 +++++++- src/ifs/wl_seat/event_handling.rs | 1 + src/ifs/wl_surface.rs | 8 +- src/ifs/wl_surface/cursor.rs | 37 +++- src/rect.rs | 7 +- src/render/renderer/framebuffer.rs | 34 ++++ src/screenshoter.rs | 1 + src/state.rs | 30 ++- src/tasks.rs | 3 +- src/tasks/connector.rs | 5 + src/tasks/hardware_cursor.rs | 31 +++ src/tree/output.rs | 3 +- src/video/drm.rs | 12 +- src/video/drm/sys.rs | 3 + 23 files changed, 614 insertions(+), 91 deletions(-) create mode 100644 src/tasks/hardware_cursor.rs diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index c3da73c5..8ba07aac 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -363,6 +363,13 @@ impl Client { self.send(&ClientMessage::SetCursorSize { seat, size }) } + pub fn set_use_hardware_cursor(&self, seat: Seat, use_hardware_cursor: bool) { + self.send(&ClientMessage::SetUseHardwareCursor { + seat, + use_hardware_cursor, + }) + } + pub fn set_size(&self, sized: Resizable, size: i32) { self.send(&ClientMessage::SetSize { sized, size }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index cce4d751..783b4aa8 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -300,6 +300,10 @@ pub enum ClientMessage<'a> { device: InputDevice, enabled: bool, }, + SetUseHardwareCursor { + seat: Seat, + use_hardware_cursor: bool, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index adcd5839..cf2caf8b 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -128,6 +128,16 @@ impl Seat { Self(raw) } + /// Sets whether this seat's cursor uses the hardware cursor if available. + /// + /// Only one seat at a time can use the hardware cursor. Setting this to `true` for a + /// seat automatically unsets it for all other seats. + /// + /// By default, the first created seat uses the hardware cursor. + pub fn use_hardware_cursor(self, use_hardware_cursor: bool) { + get!().set_use_hardware_cursor(self, use_hardware_cursor); + } + /// Sets the size of the cursor theme. /// /// Default: 16. diff --git a/src/backend.rs b/src/backend.rs index 43e327a4..546597a0 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -3,6 +3,7 @@ use { async_engine::SpawnedFuture, fixed::Fixed, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, + render::Framebuffer, video::drm::ConnectorType, }, std::{ @@ -85,11 +86,21 @@ pub trait Connector { #[derive(Debug)] pub enum ConnectorEvent { Connected(MonitorInfo), + HardwareCursor(Option>), Disconnected, Removed, ModeChanged(Mode), } +pub trait HardwareCursor: Debug { + fn set_enabled(&self, enabled: bool); + fn get_buffer(&self) -> Rc; + fn set_position(&self, x: i32, y: i32); + fn swap_buffer(&self); + fn commit(&self); + fn max_size(&self) -> (i32, i32); +} + pub type TransformMatrix = [[f64; 2]; 2]; pub trait InputDevice { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 0464b3ab..5a841809 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -3,11 +3,11 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendDrmDevice, BackendEvent, Connector, ConnectorEvent, ConnectorId, - ConnectorKernelId, DrmDeviceId, MonitorInfo, + ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, edid::Descriptor, - format::{Format, XRGB8888}, + format::{Format, ARGB8888, XRGB8888}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, render::{Framebuffer, RenderContext, RenderResult, ResetStatus, Texture}, state::State, @@ -66,6 +66,8 @@ pub struct MetalDrmDevice { pub max_width: u32, pub min_height: u32, pub max_height: u32, + pub cursor_width: u64, + pub cursor_height: u64, pub gbm: GbmDevice, pub handle_events: HandleEvents, } @@ -153,12 +155,14 @@ pub struct MetalConnector { pub can_present: Cell, pub has_damage: Cell, + pub cursor_changed: Cell, pub display: RefCell, pub connect_sent: Cell, pub primary_plane: CloneCell>>, + pub cursor_plane: CloneCell>>, pub crtc: CloneCell>>, @@ -167,6 +171,77 @@ pub struct MetalConnector { pub present_trigger: AsyncEvent, pub render_result: RefCell, + + pub cursor_generation: NumCell, + pub cursor_x: Cell, + pub cursor_y: Cell, + pub cursor_enabled: Cell, + pub cursor_buffers: CloneCell>>, + pub cursor_front_buffer: NumCell, +} + +#[derive(Debug)] +pub struct MetalHardwareCursor { + pub generation: u64, + pub connector: Rc, + pub cursor_swap_buffer: Cell, + pub cursor_enabled_pending: Cell, + pub cursor_x_pending: Cell, + pub cursor_y_pending: Cell, + pub cursor_buffers: Rc<[RenderBuffer; 2]>, + pub have_changes: Cell, +} + +impl HardwareCursor for MetalHardwareCursor { + fn set_enabled(&self, enabled: bool) { + if self.cursor_enabled_pending.replace(enabled) != enabled { + self.have_changes.set(true); + } + } + + fn get_buffer(&self) -> Rc { + let buffer = (self.connector.cursor_front_buffer.get() + 1) % 2; + self.cursor_buffers[buffer].fb.clone() + } + + fn set_position(&self, x: i32, y: i32) { + self.cursor_x_pending.set(x); + self.cursor_y_pending.set(y); + self.have_changes.set(true); + } + + fn swap_buffer(&self) { + self.cursor_swap_buffer.set(true); + self.have_changes.set(true); + } + + fn commit(&self) { + if self.generation != self.connector.cursor_generation.get() { + return; + } + if !self.have_changes.take() { + return; + } + self.connector + .cursor_enabled + .set(self.cursor_enabled_pending.get()); + self.connector.cursor_x.set(self.cursor_x_pending.get()); + self.connector.cursor_y.set(self.cursor_y_pending.get()); + if self.cursor_swap_buffer.take() { + self.connector.cursor_front_buffer.fetch_add(1); + } + self.connector.cursor_changed.set(true); + if self.connector.can_present.get() { + self.connector.schedule_present(); + } + } + + fn max_size(&self) -> (i32, i32) { + ( + self.connector.dev.cursor_width as _, + self.connector.dev.cursor_height as _, + ) + } } pub struct ConnectorFutures { @@ -201,6 +276,27 @@ impl MetalConnector { } } + fn send_hardware_cursor(self: &Rc) { + if !self.connect_sent.get() { + return; + } + let generation = self.cursor_generation.fetch_add(1) + 1; + let hc = match self.cursor_buffers.get() { + Some(cp) => Some(Rc::new(MetalHardwareCursor { + generation, + connector: self.clone(), + cursor_swap_buffer: Cell::new(false), + cursor_enabled_pending: Cell::new(self.cursor_enabled.get()), + cursor_x_pending: Cell::new(self.cursor_x.get()), + cursor_y_pending: Cell::new(self.cursor_y.get()), + cursor_buffers: cp.clone(), + have_changes: Cell::new(false), + }) as _), + _ => None, + }; + self.send_event(ConnectorEvent::HardwareCursor(hc)); + } + fn connected(&self) -> bool { let dd = self.display.borrow_mut(); dd.connection == ConnectorStatus::Connected && self.primary_plane.get().is_some() @@ -218,14 +314,11 @@ impl MetalConnector { } pub fn present(&self) { - if !self.backend.check_render_context() { - return; - } let crtc = match self.crtc.get() { Some(crtc) => crtc, _ => return, }; - if !self.has_damage.get() || !self.can_present.get() { + if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() { return; } if !crtc.active.value.get() { @@ -239,27 +332,58 @@ impl MetalConnector { Some(b) => b, _ => return, }; - let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; - if let Some(node) = self.state.root.outputs.get(&self.connector_id) { - let mut rr = self.render_result.borrow_mut(); - buffer.fb.render( - &*node, - &self.state, - Some(node.global.pos.get()), - true, - &mut rr, - node.preferred_scale.get(), - ); - for fr in rr.frame_requests.drain(..) { - fr.send_done(); - let _ = fr.client.remove_obj(&*fr); - } - node.global.perform_screencopies(&buffer.fb, &buffer.tex); - } + let cursor = self.cursor_plane.get(); let mut changes = self.master.change(); - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, buffer.drm.id().0 as _); - }); + if self.has_damage.get() { + if !self.backend.check_render_context() { + return; + } + let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; + if let Some(node) = self.state.root.outputs.get(&self.connector_id) { + let mut rr = self.render_result.borrow_mut(); + buffer.fb.render( + &*node, + &self.state, + Some(node.global.pos.get()), + true, + &mut rr, + node.preferred_scale.get(), + !self.cursor_enabled.get(), + ); + for fr in rr.frame_requests.drain(..) { + fr.send_done(); + let _ = fr.client.remove_obj(&*fr); + } + node.global.perform_screencopies(&buffer.fb, &buffer.tex); + } + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, buffer.drm.id().0 as _); + }); + } + if self.cursor_changed.get() && cursor.is_some() { + let plane = cursor.unwrap(); + if self.cursor_enabled.get() { + let buffers = self.cursor_buffers.get().unwrap(); + let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()]; + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, buffer.drm.id().0 as _); + c.change(plane.crtc_id.id, crtc.id.0 as _); + c.change(plane.crtc_x.id, self.cursor_x.get() as _); + c.change(plane.crtc_y.id, self.cursor_y.get() as _); + c.change(plane.crtc_w.id, buffer.tex.width() as _); + c.change(plane.crtc_h.id, buffer.tex.height() as _); + c.change(plane.src_x.id, 0); + c.change(plane.src_y.id, 0); + c.change(plane.src_w.id, (buffer.tex.width() as u64) << 16); + c.change(plane.src_h.id, (buffer.tex.height() as u64) << 16); + }); + } else { + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, 0); + c.change(plane.crtc_id.id, 0); + }); + } + } if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) { match e { DrmError::Atomic(OsError(c::EACCES)) => { @@ -270,6 +394,7 @@ impl MetalConnector { } self.can_present.set(false); self.has_damage.set(false); + self.cursor_changed.set(false); } } @@ -346,6 +471,8 @@ pub struct MetalPlane { pub possible_crtcs: u32, pub formats: AHashMap, + pub assigned: Cell, + pub crtc_id: MutableProperty, pub crtc_x: MutableProperty, pub crtc_y: MutableProperty, @@ -404,12 +531,20 @@ fn create_connector( can_present: Cell::new(true), has_damage: Cell::new(true), primary_plane: Default::default(), + cursor_plane: Default::default(), crtc: Default::default(), on_change: Default::default(), present_trigger: Default::default(), render_result: RefCell::new(Default::default()), + cursor_generation: Default::default(), + cursor_x: Cell::new(0), + cursor_y: Cell::new(0), + cursor_enabled: Cell::new(false), + cursor_buffers: Default::default(), display: RefCell::new(display), connect_sent: Cell::new(false), + cursor_changed: Cell::new(false), + cursor_front_buffer: Default::default(), }); let futures = ConnectorFutures { present: backend @@ -629,6 +764,7 @@ fn create_plane(plane: DrmPlane, master: &Rc) -> Result, dd: &ConnectorDisplayData) { let mut prev_mode = None; let mut modes = vec![]; for mode in dd.modes.iter().map(|m| m.to_backend()) { @@ -874,6 +1010,7 @@ impl MetalBackend { height_mm: dd.mm_height as _, })); connector.connect_sent.set(true); + connector.send_hardware_cursor(); } pub fn create_drm_device( @@ -886,6 +1023,14 @@ impl MetalBackend { } let resources = master.get_resources()?; + let (cursor_width, cursor_height) = match master.get_cursor_size() { + Ok(s) => s, + Err(e) => { + log::warn!("Can't determine size of cursor planes: {}", ErrorFmt(e)); + (64, 64) + } + }; + let mut planes = AHashMap::new(); for plane in master.get_planes()? { match create_plane(plane, master) { @@ -933,6 +1078,8 @@ impl MetalBackend { max_width: resources.max_width, min_height: resources.min_height, max_height: resources.max_height, + cursor_width, + cursor_height, gbm, handle_events: HandleEvents { handle_events: Cell::new(None), @@ -1019,6 +1166,7 @@ impl MetalBackend { for connector in dev.connectors.lock().values() { connector.can_present.set(true); connector.has_damage.set(true); + connector.cursor_changed.set(true); } if dev.unprocessed_change.get() { return self.handle_drm_change_(dev, false); @@ -1083,7 +1231,7 @@ impl MetalBackend { _ => return, }; connector.can_present.set(true); - if connector.has_damage.get() { + if connector.has_damage.get() || connector.cursor_changed.get() { connector.schedule_present(); } let dd = connector.display.borrow_mut(); @@ -1123,6 +1271,7 @@ impl MetalBackend { continue; } plane.crtc_id.value.set(DrmCrtc::NONE); + plane.assigned.set(false); changes.change_object(plane.id, |c| { c.change(plane.crtc_id.id, 0); c.change(plane.fb_id, 0); @@ -1135,19 +1284,15 @@ impl MetalBackend { &self, dev: &MetalDrmDeviceData, changes: &mut Change, - preserve: &mut Preserve, + preserve: &Preserve, ) { for connector in dev.connectors.lock().values() { if preserve.connectors.contains(&connector.id) { - if let Some(pp) = connector.primary_plane.get() { - preserve.planes.insert(pp.id); - } - if let Some(crtc) = connector.crtc.get() { - preserve.crtcs.insert(crtc.id); - } continue; } connector.primary_plane.set(None); + connector.cursor_plane.set(None); + connector.cursor_enabled.set(false); connector.crtc.set(None); let dd = connector.display.borrow_mut(); dd.crtc_id.value.set(DrmCrtc::NONE); @@ -1220,11 +1365,31 @@ impl MetalBackend { fail!(c.id); } } + if let Some(plane) = c.cursor_plane.get() { + let crtc_id = plane.crtc_id.value.get(); + if crtc_id.is_some() && crtc_id != crtc.id { + log::warn!("Cannot preserve connector whose cursor plane is attached to a different crtc"); + fail!(c.id); + } + } } } for c in remove_connectors { preserve.connectors.remove(&c); } + for connector in dev.connectors.lock().values() { + if preserve.connectors.contains(&connector.id) { + if let Some(pp) = connector.primary_plane.get() { + preserve.planes.insert(pp.id); + } + if let Some(pp) = connector.cursor_plane.get() { + preserve.planes.insert(pp.id); + } + if let Some(crtc) = connector.crtc.get() { + preserve.crtcs.insert(crtc.id); + } + } + } } fn init_drm_device( @@ -1256,7 +1421,7 @@ impl MetalBackend { for connector in dev.connectors.lock().values() { if !preserve.connectors.contains(&connector.id) { if let Err(e) = - self.assign_connector_plane(connector, &mut changes, &ctx, &mut old_buffers) + self.assign_connector_planes(connector, &mut changes, &ctx, &mut old_buffers) { log::error!("Could not assign a plane: {}", ErrorFmt(e)); } @@ -1265,6 +1430,12 @@ impl MetalBackend { if let Err(e) = changes.commit(flags, 0) { return Err(MetalError::Modeset(e)); } + for connector in dev.connectors.lock().values() { + if preserve.connectors.contains(&connector.id) { + continue; + } + connector.send_hardware_cursor(); + } Ok(()) } @@ -1355,8 +1526,9 @@ impl MetalBackend { width: i32, height: i32, ctx: &MetalRenderContext, + cursor: bool, ) -> Result<[RenderBuffer; 2], MetalError> { - let create = || self.create_scanout_buffer(dev, format, width, height, ctx); + let create = || self.create_scanout_buffer(dev, format, width, height, ctx, cursor); Ok([create()?, create()?]) } @@ -1367,9 +1539,10 @@ impl MetalBackend { width: i32, height: i32, ctx: &MetalRenderContext, + cursor: bool, ) -> Result { let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; - if ctx.dev.id != dev.id { + if cursor || ctx.dev.id != dev.id { usage |= GBM_BO_USE_LINEAR; }; let bo = dev.gbm.create_bo(width, height, format, usage); @@ -1439,7 +1612,7 @@ impl MetalBackend { Ok(()) } - fn assign_connector_plane( + fn assign_connector_planes( &self, connector: &Rc, changes: &mut Change, @@ -1461,7 +1634,7 @@ impl MetalBackend { let primary_plane = 'primary_plane: { for plane in crtc.possible_planes.values() { if plane.ty == PlaneType::Primary - && plane.crtc_id.value.get().is_none() + && !plane.assigned.get() && plane.formats.contains_key(&XRGB8888.drm) { break 'primary_plane plane.clone(); @@ -1469,17 +1642,51 @@ impl MetalBackend { } return Err(MetalError::NoPrimaryPlaneForConnector); }; - let format = ModifiedFormat { - format: XRGB8888, - modifier: INVALID_MODIFIER, - }; let buffers = Rc::new(self.create_scanout_buffers( &connector.dev, - &format, + &ModifiedFormat { + format: XRGB8888, + modifier: INVALID_MODIFIER, + }, mode.hdisplay as _, mode.vdisplay as _, ctx, + false, )?); + let mut cursor_plane = None; + for plane in crtc.possible_planes.values() { + if plane.ty == PlaneType::Cursor + && !plane.assigned.get() + && plane.formats.contains_key(&ARGB8888.drm) + { + cursor_plane = Some(plane.clone()); + break; + } + } + let mut cursor_buffers = None; + if cursor_plane.is_some() { + let res = self.create_scanout_buffers( + &connector.dev, + &ModifiedFormat { + format: ARGB8888, + modifier: INVALID_MODIFIER, + }, + connector.dev.cursor_width as _, + connector.dev.cursor_height as _, + ctx, + true, + ); + match res { + Ok(r) => cursor_buffers = Some(Rc::new(r)), + Err(e) => { + log::warn!( + "Could not allocate buffers for the cursor plane: {}", + ErrorFmt(e) + ); + cursor_plane = None; + } + } + } changes.change_object(primary_plane.id, |c| { c.change(primary_plane.fb_id, buffers[0].drm.id().0 as _); c.change(primary_plane.crtc_id.id, crtc.id.0 as _); @@ -1492,6 +1699,7 @@ impl MetalBackend { c.change(primary_plane.src_w.id, (mode.hdisplay as u64) << 16); c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16); }); + primary_plane.assigned.set(true); primary_plane.crtc_id.value.set(crtc.id); primary_plane.crtc_x.value.set(0); primary_plane.crtc_y.value.set(0); @@ -1505,6 +1713,14 @@ impl MetalBackend { old_buffers.push(old); } connector.primary_plane.set(Some(primary_plane.clone())); + if let Some(cp) = &cursor_plane { + cp.assigned.set(true); + } + if let Some(old) = connector.cursor_buffers.set(cursor_buffers) { + old_buffers.push(old); + } + connector.cursor_plane.set(cursor_plane); + connector.cursor_enabled.set(false); Ok(()) } @@ -1516,6 +1732,7 @@ impl MetalBackend { dd.mode.as_ref().unwrap(), ); connector.has_damage.set(true); + connector.cursor_changed.set(true); connector.schedule_present(); } } diff --git a/src/backends/x.rs b/src/backends/x.rs index 1bffa8d1..a1085620 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -700,6 +700,7 @@ impl XBackend { true, rr.deref_mut(), node.preferred_scale.get(), + true, ); for fr in rr.frame_requests.drain(..) { fr.send_done(); diff --git a/src/compositor.rs b/src/compositor.rs index 0e81cffd..40882be7 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -193,6 +193,7 @@ fn start_compositor2( }, scales, cursor_sizes: Default::default(), + hardware_tick_cursor: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -274,6 +275,7 @@ fn start_global_event_handlers( res.push(eng.spawn(tasks::handle_backend_events(state.clone()))); res.push(eng.spawn(tasks::handle_slow_clients(state.clone()))); + res.push(eng.spawn(tasks::handle_hardware_cursor_tick(state.clone()))); res.push(eng.spawn2(Phase::Layout, container_layout(state.clone()))); res.push(eng.spawn2(Phase::PostLayout, container_render_data(state.clone()))); res.push(eng.spawn2(Phase::Layout, float_layout(state.clone()))); @@ -383,6 +385,7 @@ fn create_dummy_output(state: &Rc) { pointer_positions: Default::default(), lock_surface: Default::default(), preferred_scale: Cell::new(Fixed::from_int(1)), + hardware_cursor: Default::default(), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/config/handler.rs b/src/config/handler.rs index d9a0eff1..dc5ae142 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -605,6 +605,24 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_use_hardware_cursor( + &self, + seat: Seat, + use_hardware_cursor: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if use_hardware_cursor { + for other in self.state.globals.seats.lock().values() { + if other.id() != seat.id() { + other.set_hardware_cursor(false); + } + } + } + seat.set_hardware_cursor(use_hardware_cursor); + self.state.refresh_hardware_cursors(); + Ok(()) + } + fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; let pos = connector.node.global.pos.get(); @@ -1171,6 +1189,12 @@ impl ConfigProxyHandler { ClientMessage::SetDragLockEnabled { device, enabled } => self .handle_set_drag_lock_enabled(device, enabled) .wrn("set_drag_lock_enabled")?, + ClientMessage::SetUseHardwareCursor { + seat, + use_hardware_cursor, + } => self + .handle_set_use_hardware_cursor(seat, use_hardware_cursor) + .wrn("set_use_hardware_cursor")?, } Ok(()) } diff --git a/src/cursor.rs b/src/cursor.rs index c0e631b1..eedd0dd7 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -5,6 +5,7 @@ use { rect::Rect, render::{RenderContext, RenderError, Renderer, Texture}, state::State, + time::Time, tree::OutputNode, utils::{errorfmt::ErrorFmt, numcell::NumCell, smallmap::SmallMapMut}, }, @@ -22,9 +23,9 @@ use { mem::MaybeUninit, rc::Rc, slice, str, + time::Duration, }, thiserror::Error, - uapi::c, }; const XCURSOR_MAGIC: u32 = 0x72756358; @@ -38,11 +39,19 @@ const HEADER_SIZE: u32 = 16; pub trait Cursor { fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed); + fn render_hardware_cursor(&self, renderer: &mut Renderer); + fn extents_at_scale(&self, scale: Fixed) -> Rect; fn set_output(&self, output: &Rc) { let _ = output; } fn handle_unset(&self) {} fn tick(&self) {} + fn needs_tick(&self) -> bool { + false + } + fn time_until_tick(&self) -> Duration { + Duration::new(0, 0) + } } pub struct ServerCursors { @@ -183,19 +192,12 @@ impl ServerCursorTemplate { ServerCursorTemplateVariant::Static(s) => Rc::new(StaticCursor { image: s.for_size(size), }), - ServerCursorTemplateVariant::Animated(a) => { - let mut start = c::timespec { - tv_sec: 0, - tv_nsec: 0, - }; - uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut start).unwrap(); - Rc::new(AnimatedCursor { - start, - next: NumCell::new(a[0].delay_ns), - idx: Cell::new(0), - images: a.iter().map(|c| c.for_size(size)).collect(), - }) - } + ServerCursorTemplateVariant::Animated(a) => Rc::new(AnimatedCursor { + start: Time::now_unchecked(), + next: NumCell::new(a[0].delay_ns), + idx: Cell::new(0), + images: a.iter().map(|c| c.for_size(size)).collect(), + }), } } } @@ -237,7 +239,7 @@ impl CursorImage { sizes: SmallMapMut<(Fixed, u32), Rc, 2>, ) -> Result { Ok(Self { - delay_ns: delay_ms * 1_000_000, + delay_ns: delay_ms.max(1) * 1_000_000, sizes, }) } @@ -291,10 +293,23 @@ impl Cursor for StaticCursor { fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) { render_img(&self.image, renderer, x, y); } + + fn render_hardware_cursor(&self, renderer: &mut Renderer) { + if let Some(img) = self.image.scales.get(&renderer.scale()) { + renderer.render_texture(&img.tex, 0, 0, ARGB8888, None, None, renderer.scale()); + } + } + + fn extents_at_scale(&self, scale: Fixed) -> Rect { + match self.image.scales.get(&scale) { + None => Rect::new_empty(0, 0), + Some(i) => i.extents, + } + } } struct AnimatedCursor { - start: c::timespec, + start: Time, next: NumCell, idx: Cell, images: Vec, @@ -306,15 +321,24 @@ impl Cursor for AnimatedCursor { render_img(img, renderer, x, y); } + fn render_hardware_cursor(&self, renderer: &mut Renderer) { + let img = &self.images[self.idx.get()]; + if let Some(img) = img.scales.get(&renderer.scale()) { + renderer.render_texture(&img.tex, 0, 0, ARGB8888, None, None, renderer.scale()); + } + } + + fn extents_at_scale(&self, scale: Fixed) -> Rect { + let img = &self.images[self.idx.get()]; + match img.scales.get(&scale) { + None => Rect::new_empty(0, 0), + Some(i) => i.extents, + } + } + fn tick(&self) { - let mut now = c::timespec { - tv_sec: 0, - tv_nsec: 0, - }; - uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut now).unwrap(); - let dist = (now.tv_sec.wrapping_sub(self.start.tv_sec)) as i64 * 1_000_000_000 - + now.tv_nsec.wrapping_sub(self.start.tv_nsec) as i64; - if (dist as u64) < self.next.get() { + let dist = Time::now_unchecked() - self.start; + if (dist.as_nanos() as u64) < self.next.get() { return; } let idx = (self.idx.get() + 1) % self.images.len(); @@ -322,6 +346,17 @@ impl Cursor for AnimatedCursor { let image = &self.images[idx]; self.next.fetch_add(image.delay_ns); } + + fn needs_tick(&self) -> bool { + true + } + + fn time_until_tick(&self) -> Duration { + let dist = Time::now_unchecked() - self.start; + let dist = dist.as_nanos() as u64; + let nanos = self.next.get().saturating_sub(dist); + Duration::from_nanos(nanos) + } } struct OpenCursorResult { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index ab3ea5a6..7056ade1 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -38,6 +38,7 @@ use { }, leaks::Tracker, object::Object, + rect::Rect, state::State, tree::{ generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode, @@ -150,6 +151,7 @@ pub struct WlSeatGlobal { desired_known_cursor: Cell>, changes: NumCell, cursor_size: Cell, + hardware_cursor: Cell, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -202,6 +204,7 @@ impl WlSeatGlobal { desired_known_cursor: Cell::new(None), changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE), cursor_size: Cell::new(DEFAULT_CURSOR_SIZE), + hardware_cursor: Cell::new(state.globals.seats.len() == 0), }); state.add_cursor_size(DEFAULT_CURSOR_SIZE); let seat = slf.clone(); @@ -218,6 +221,78 @@ impl WlSeatGlobal { slf } + pub fn set_hardware_cursor(&self, hardware_cursor: bool) { + self.hardware_cursor.set(hardware_cursor); + } + + pub fn hardware_cursor(&self) -> bool { + self.hardware_cursor.get() + } + + fn update_hardware_cursor_position(&self) { + self.update_hardware_cursor_(false); + } + + pub fn update_hardware_cursor(&self) { + self.update_hardware_cursor_(true); + } + + fn update_hardware_cursor_(&self, render: bool) { + if !self.hardware_cursor.get() { + return; + } + let cursor = match self.get_cursor() { + Some(c) => c, + _ => { + self.state.disable_hardware_cursors(); + return; + } + }; + if render { + cursor.tick(); + } + let (x, y) = self.get_position(); + for output in self.state.root.outputs.lock().values() { + if let Some(hc) = output.hardware_cursor.get() { + let scale = output.preferred_scale.get(); + let extents = cursor.extents_at_scale(scale); + if render { + let (max_width, max_height) = hc.max_size(); + if extents.width() > max_width || extents.height() > max_height { + hc.set_enabled(false); + hc.commit(); + continue; + } + } + let opos = output.global.pos.get(); + let (x_rel, y_rel); + if scale == 1 { + x_rel = x.round_down() - opos.x1(); + y_rel = y.round_down() - opos.y1(); + } else { + let scalef = scale.to_f64(); + x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; + y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; + } + let mode = output.global.mode.get(); + if extents + .intersects(&Rect::new_sized(-x_rel, -y_rel, mode.width, mode.height).unwrap()) + { + if render { + let buffer = hc.get_buffer(); + buffer.render_hardware_cursor(cursor.deref(), &self.state, scale); + hc.swap_buffer(); + } + hc.set_enabled(true); + hc.set_position(x_rel + extents.x1(), y_rel + extents.y1()); + } else { + hc.set_enabled(false); + } + hc.commit(); + } + } + } + pub fn set_cursor_size(&self, size: u32) { let old = self.cursor_size.replace(size); if size != old { @@ -361,6 +436,7 @@ impl WlSeatGlobal { pub fn set_position(&self, x: i32, y: i32) { self.pos.set((Fixed::from_int(x), Fixed::from_int(y))); + self.update_hardware_cursor_position(); self.trigger_tree_changed(); let output = 'set_output: { let outputs = self.state.outputs.lock(); @@ -681,7 +757,9 @@ impl WlSeatGlobal { if let Some(cursor) = cursor.as_ref() { cursor.set_output(&self.output.get()); } - self.cursor.set(cursor); + self.cursor.set(cursor.clone()); + self.state.hardware_tick_cursor.push(cursor); + self.update_hardware_cursor(); } pub fn dnd_icon(&self) -> Option> { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 09470f6e..63fb8562 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -480,6 +480,7 @@ impl WlSeatGlobal { fn set_new_position(self: &Rc, time_usec: u64, x: Fixed, y: Fixed) { self.pos_time_usec.set(time_usec); self.pos.set((x, y)); + self.update_hardware_cursor_position(); self.changes.or_assign(CHANGE_CURSOR_MOVED); self.apply_changes(); } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index c43abb52..11442214 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -804,9 +804,6 @@ impl WlSurface { } self.buffer_abs_pos .set(self.buffer_abs_pos.get().with_size(width, height).unwrap()); - for (_, cursor) in &self.cursors { - cursor.handle_buffer_change(); - } } { let mut pfr = self.pending.frame_request.borrow_mut(); @@ -832,6 +829,11 @@ impl WlSurface { if self.need_extents_update.get() { self.calculate_extents(); } + if buffer_transform_changed || transform_changed { + for (_, cursor) in &self.cursors { + cursor.handle_buffer_change(); + } + } ext.post_commit(); self.client.state.damage(); Ok(()) diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 253e04b1..11fb17d8 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -31,10 +31,12 @@ impl CursorSurface { } fn update_extents(&self) { - let extents = self.extents.get(); let (hot_x, hot_y) = self.hotspot.get(); self.extents - .set(Rect::new_sized(-hot_x, -hot_y, extents.width(), extents.height()).unwrap()); + .set(self.surface.extents.get().move_(-hot_x, -hot_y)); + if self.seat.hardware_cursor() { + self.seat.update_hardware_cursor(); + } } pub fn handle_surface_destroy(&self) { @@ -42,9 +44,6 @@ impl CursorSurface { } pub fn handle_buffer_change(&self) { - let (width, height) = self.surface.buffer_abs_pos.get().size(); - self.extents - .set(Rect::new_sized(0, 0, width, height).unwrap()); self.update_extents(); } @@ -62,22 +61,44 @@ impl CursorSurface { impl Cursor for CursorSurface { fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) { - let extents = self.extents.get().move_(x.round_down(), y.round_down()); + let x_int = x.round_down(); + let y_int = y.round_down(); + let extents = self.extents.get().move_(x_int, y_int); if extents.intersects(&renderer.logical_extents()) { + let (hot_x, hot_y) = self.hotspot.get(); let scale = renderer.scale(); if scale != 1 { let scale = scale.to_f64(); - let (hot_x, hot_y) = self.hotspot.get(); let (hot_x, hot_y) = (Fixed::from_int(hot_x), Fixed::from_int(hot_y)); let x = ((x - hot_x).to_f64() * scale).round() as _; let y = ((y - hot_y).to_f64() * scale).round() as _; renderer.render_surface_scaled(&self.surface, x, y, None); } else { - renderer.render_surface(&self.surface, extents.x1(), extents.y1()); + renderer.render_surface(&self.surface, x_int - hot_x, y_int - hot_y); } } } + fn render_hardware_cursor(&self, renderer: &mut Renderer) { + let extents = self.surface.extents.get(); + renderer.render_surface(&self.surface, -extents.x1(), -extents.y1()); + } + + fn extents_at_scale(&self, scale: Fixed) -> Rect { + let rect = self.extents.get(); + if scale == 1 { + return rect; + } + let scale = scale.to_f64(); + Rect::new( + (rect.x1() as f64 * scale).ceil() as _, + (rect.y1() as f64 * scale).ceil() as _, + (rect.x2() as f64 * scale).ceil() as _, + (rect.y2() as f64 * scale).ceil() as _, + ) + .unwrap() + } + fn set_output(&self, output: &Rc) { self.surface.set_output(output); } diff --git a/src/rect.rs b/src/rect.rs index d99e3117..8bd03191 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -99,13 +99,8 @@ impl Rect { } } - #[allow(dead_code)] pub fn intersects(&self, other: &Self) -> bool { - let x1 = self.x1.max(other.x1); - let y1 = self.y1.max(other.y1); - let x2 = self.x2.min(other.x2); - let y2 = self.y2.min(other.y2); - x1 < x2 && y1 < y2 + self.x1 < other.x2 && other.x1 < self.x2 && self.y1 < other.y2 && other.y1 < self.y2 } pub fn intersect(&self, other: Self) -> Self { diff --git a/src/render/renderer/framebuffer.rs b/src/render/renderer/framebuffer.rs index 1549a14e..18818c4a 100644 --- a/src/render/renderer/framebuffer.rs +++ b/src/render/renderer/framebuffer.rs @@ -1,5 +1,6 @@ use { crate::{ + cursor::Cursor, fixed::Fixed, format::{Format, XRGB8888}, rect::Rect, @@ -113,6 +114,7 @@ impl Framebuffer { on_output: bool, result: &mut RenderResult, scale: Fixed, + render_hardware_cursor: bool, ) { let _ = self.ctx.ctx.with_current(|| { let c = state.theme.colors.background.get(); @@ -138,6 +140,9 @@ impl Framebuffer { if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { + if !render_hardware_cursor && seat.hardware_cursor() { + continue; + } if let Some(cursor) = seat.get_cursor() { let (mut x, mut y) = seat.get_position(); if let Some(dnd_icon) = seat.dnd_icon() { @@ -163,4 +168,33 @@ impl Framebuffer { Ok(()) }); } + + pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Fixed) { + let _ = self.ctx.ctx.with_current(|| { + unsafe { + glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); + glViewport(0, 0, self.gl.width, self.gl.height); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + let mut res = RenderResult::default(); + let mut renderer = Renderer { + ctx: &self.ctx, + fb: &self.gl, + state, + on_output: false, + result: &mut res, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + logical_extents: Rect::new_empty(0, 0), + }; + cursor.render_hardware_cursor(&mut renderer); + unsafe { + glFlush(); + } + Ok(()) + }); + } } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index de4f86ba..258e4186 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -61,6 +61,7 @@ pub fn take_screenshot(state: &State) -> Result false, &mut Default::default(), Fixed::from_int(1), + true, ); let drm = ctx.gbm.drm.dup_render()?.fd().clone(); Ok(Screenshot { drm, bo }) diff --git a/src/state.rs b/src/state.rs index f96ca385..be1c8ea5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,7 +10,7 @@ use { cli::RunArgs, client::{Client, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES}, config::ConfigProxy, - cursor::ServerCursors, + cursor::{Cursor, ServerCursors}, dbus::Dbus, fixed::Fixed, forker::ForkerProxy, @@ -113,6 +113,7 @@ pub struct State { pub lock: ScreenlockState, pub scales: RefCounted, pub cursor_sizes: RefCounted, + pub hardware_tick_cursor: AsyncQueue>>, } // impl Drop for State { @@ -594,4 +595,31 @@ impl State { self.wheel.clear(); self.eng.clear(); } + + pub fn disable_hardware_cursors(&self) { + for output in self.root.outputs.lock().values() { + if let Some(hc) = output.hardware_cursor.get() { + hc.set_enabled(false); + hc.commit(); + } + } + } + + pub fn refresh_hardware_cursors(&self) { + let seat = self + .globals + .seats + .lock() + .values() + .find(|s| s.hardware_cursor()) + .cloned(); + let seat = match seat { + Some(s) => s, + _ => { + self.disable_hardware_cursors(); + return; + } + }; + seat.update_hardware_cursor(); + } } diff --git a/src/tasks.rs b/src/tasks.rs index e20b1037..7df2c4fa 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,11 +1,11 @@ mod backend; mod connector; mod drmdev; +mod hardware_cursor; mod idle; mod input_device; mod slow_clients; -pub use idle::idle; use { crate::{ state::State, @@ -13,6 +13,7 @@ use { }, std::rc::Rc, }; +pub use {hardware_cursor::handle_hardware_cursor_tick, idle::idle}; pub async fn handle_backend_events(state: Rc) { let mut beh = BackendEventHandler { state }; diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 3428beef..f2433b37 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -123,6 +123,7 @@ impl ConnectorHandler { pointer_positions: Default::default(), lock_surface: Default::default(), preferred_scale: Cell::new(Fixed::from_int(1)), + hardware_cursor: Default::default(), }); self.state.add_output_scale(on.preferred_scale.get()); let mode = info.initial_mode; @@ -191,6 +192,10 @@ impl ConnectorHandler { while let Some(event) = self.data.connector.event() { match event { ConnectorEvent::Disconnected => break 'outer, + ConnectorEvent::HardwareCursor(hc) => { + on.hardware_cursor.set(hc); + self.state.refresh_hardware_cursors(); + } ConnectorEvent::ModeChanged(mode) => { on.update_mode(mode); } diff --git a/src/tasks/hardware_cursor.rs b/src/tasks/hardware_cursor.rs new file mode 100644 index 00000000..65e43092 --- /dev/null +++ b/src/tasks/hardware_cursor.rs @@ -0,0 +1,31 @@ +use { + crate::{state::State, utils::errorfmt::ErrorFmt}, + futures_util::{select, FutureExt}, + std::rc::Rc, +}; + +pub async fn handle_hardware_cursor_tick(state: Rc) { + loop { + let cursor = match state.hardware_tick_cursor.pop().await { + Some(c) => c, + _ => continue, + }; + if !cursor.needs_tick() { + continue; + } + loop { + let tick = (cursor.time_until_tick().as_nanos() + 999_999) / 1_000_000; + if tick > 0 { + let res = select! { + _ = state.hardware_tick_cursor.non_empty().fuse() => break, + res = state.wheel.timeout(tick as _).fuse() => res, + }; + if let Err(e) = res { + log::error!("Could not wait for cursor tick: {}", ErrorFmt(e)); + break; + } + } + state.refresh_hardware_cursors(); + } + } +} diff --git a/src/tree/output.rs b/src/tree/output.rs index 8e92dcc2..f4c7bc69 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{KeyState, Mode}, + backend::{HardwareCursor, KeyState, Mode}, cursor::KnownCursor, fixed::Fixed, ifs::{ @@ -52,6 +52,7 @@ pub struct OutputNode { pub pointer_positions: CopyHashMap, pub lock_surface: CloneCell>>, pub preferred_scale: Cell, + pub hardware_cursor: CloneCell>>, } impl OutputNode { diff --git a/src/video/drm.rs b/src/video/drm.rs index 417a2fed..8d2d097c 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -32,7 +32,11 @@ use { use crate::{ backend, utils::{errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt}, - video::{dmabuf::DmaBuf, INVALID_MODIFIER}, + video::{ + dmabuf::DmaBuf, + drm::sys::{DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH}, + INVALID_MODIFIER, + }, }; pub use sys::{ drm_mode_modeinfo, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, @@ -235,6 +239,12 @@ impl DrmMaster { mode_getencoder(self.raw(), encoder.0) } + pub fn get_cursor_size(&self) -> Result<(u64, u64), OsError> { + let width = self.get_cap(DRM_CAP_CURSOR_WIDTH)?; + let height = self.get_cap(DRM_CAP_CURSOR_HEIGHT)?; + Ok((width, height)) + } + pub fn get_connector_info( &self, connector: DrmConnector, diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 644e3ad3..48cd71bd 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -217,6 +217,9 @@ const DRM_MODE_PROP_SIGNED_RANGE: u32 = drm_mode_prop_type(2); const DRM_MODE_PROP_ATOMIC: u32 = 0x80000000; +pub const DRM_CAP_CURSOR_WIDTH: u64 = 0x8; +pub const DRM_CAP_CURSOR_HEIGHT: u64 = 0x9; + #[repr(C)] struct drm_mode_property_enum { value: u64,