From 93bfb9c0b4e06badf3a7a70de68fd682d1224fd1 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Sep 2024 17:02:09 +0200 Subject: [PATCH] metal: latch hardware cursors in the backend --- src/backend.rs | 16 ++-- src/backends/metal/video.rs | 181 +++++++++++++++++------------------ src/cursor.rs | 1 + src/cursor_user.rs | 156 ++++++++++++++++-------------- src/output_schedule.rs | 7 +- src/state.rs | 26 +++-- src/tasks/hardware_cursor.rs | 3 +- 7 files changed, 204 insertions(+), 186 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 81c69213..772db6ff 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -135,17 +135,19 @@ pub enum ConnectorEvent { FormatsChanged(Rc>, &'static Format), } -pub trait HardwareCursor: Debug { - fn set_enabled(&self, enabled: bool); +pub trait HardwareCursorUpdate { + fn set_enabled(&mut self, enabled: bool); fn get_buffer(&self) -> Rc; - fn set_position(&self, x: i32, y: i32); - fn swap_buffer(&self); - fn set_sync_file(&self, sync_file: Option); - fn commit(&self, schedule_present: bool); - fn schedule_present(&self) -> bool; + fn set_position(&mut self, x: i32, y: i32); + fn swap_buffer(&mut self); + fn set_sync_file(&mut self, sync_file: Option); fn size(&self) -> (i32, i32); } +pub trait HardwareCursor: Debug { + fn damage(&self); +} + pub type TransformMatrix = [[f64; 2]; 2]; linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 3f4a55c7..992c8f3b 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -4,8 +4,8 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector, - ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, Mode, - MonitorInfo, + ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, + HardwareCursorUpdate, Mode, MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, drm_feedback::DrmFeedback, @@ -428,7 +428,7 @@ pub struct MetalConnector { pub can_present: Cell, pub has_damage: Cell, pub cursor_changed: Cell, - pub cursor_scheduled: Cell, + pub cursor_damage: Cell, pub next_flip_nsec: Cell, pub display: RefCell, @@ -446,11 +446,10 @@ pub struct MetalConnector { 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_buffers: CloneCell>>, pub cursor_front_buffer: NumCell, pub cursor_swap_buffer: Cell, pub cursor_sync_file: CloneCell>, @@ -472,15 +471,17 @@ impl Debug for MetalConnector { } 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; 3]>, - pub sync_file: CloneCell>, - pub have_changes: Cell, +} + +pub struct MetalHardwareCursorChange<'a> { + pub cursor_swap_buffer: bool, + pub cursor_enabled: bool, + pub cursor_x: i32, + pub cursor_y: i32, + pub cursor_buffer: &'a RenderBuffer, + pub sync_file: Option, + pub cursor_size: (i32, i32), } impl Debug for MetalHardwareCursor { @@ -491,72 +492,38 @@ impl Debug for MetalHardwareCursor { } impl HardwareCursor for MetalHardwareCursor { - fn set_enabled(&self, enabled: bool) { - if self.cursor_enabled_pending.replace(enabled) != enabled { - self.have_changes.set(true); + fn damage(&self) { + self.connector.cursor_damage.set(true); + if self.connector.can_present.get() { + self.connector.schedule_present(); } } +} + +impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { + fn set_enabled(&mut self, enabled: bool) { + self.cursor_enabled = enabled; + } fn get_buffer(&self) -> Rc { - let buffer = (self.connector.cursor_front_buffer.get() + 1) % self.cursor_buffers.len(); - self.cursor_buffers[buffer].render_fb() + self.cursor_buffer.render_fb() } - 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 set_position(&mut self, x: i32, y: i32) { + self.cursor_x = x; + self.cursor_y = y; } - fn swap_buffer(&self) { - self.cursor_swap_buffer.set(true); - self.have_changes.set(true); + fn swap_buffer(&mut self) { + self.cursor_swap_buffer = true; } - fn set_sync_file(&self, sync_file: Option) { - self.sync_file.set(sync_file); - self.have_changes.set(true); - } - - fn commit(&self, schedule_present: bool) { - 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_swap_buffer.set(true); - } - self.connector.cursor_sync_file.set(self.sync_file.take()); - self.connector.cursor_changed.set(true); - if schedule_present { - self.schedule_present(); - } - } - - fn schedule_present(&self) -> bool { - if self.connector.cursor_changed.get() { - self.connector.cursor_scheduled.set(true); - if self.connector.can_present.get() { - self.connector.schedule_present(); - } - true - } else { - false - } + fn set_sync_file(&mut self, sync_file: Option) { + self.sync_file = sync_file; } fn size(&self) -> (i32, i32) { - ( - self.connector.dev.cursor_width as _, - self.connector.dev.cursor_height as _, - ) + self.cursor_size } } @@ -622,7 +589,12 @@ impl MetalConnector { self.state.ring.timeout(next_present).await.unwrap(); } } - match self.present(true) { + let Some(node) = self.state.root.outputs.get(&self.connector_id) else { + return; + }; + self.latch_cursor(&node); + node.schedule.latched(); + match self.present(&node, true) { Ok(_) => self.state.set_backend_idle(false), Err(e) => { log::error!("Could not present: {}", ErrorFmt(e)); @@ -671,21 +643,11 @@ impl MetalConnector { | FrontState::Connected { non_desktop: true } => return, FrontState::Connected { non_desktop: false } => {} } - let generation = self.cursor_generation.fetch_add(1) + 1; - let hc = match self.cursor_buffers.get() { - Some(cp) => Some(Rc::new(MetalHardwareCursor { - generation, + let hc = self.cursor_buffers.is_some().then(|| { + Rc::new(MetalHardwareCursor { 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(), - sync_file: Default::default(), - have_changes: Cell::new(false), - }) as _), - _ => None, - }; + }) as _ + }); self.on_change .send_event(ConnectorEvent::HardwareCursor(hc)); } @@ -948,12 +910,48 @@ impl MetalConnector { }) } - pub fn present(&self, try_direct_scanout: bool) -> Result<(), MetalError> { + fn latch_cursor(&self, node: &Rc) { + if !self.cursor_damage.take() { + return; + } + if self.cursor_plane.is_none() { + return; + } + let buffers = self.cursor_buffers.get().unwrap(); + let mut c = MetalHardwareCursorChange { + cursor_enabled: self.cursor_enabled.get(), + cursor_swap_buffer: false, + cursor_x: self.cursor_x.get(), + cursor_y: self.cursor_y.get(), + cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], + sync_file: None, + cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), + }; + self.state.present_hardware_cursor(node, &mut c); + self.cursor_swap_buffer.set(c.cursor_swap_buffer); + if c.sync_file.is_some() { + self.cursor_sync_file.set(c.sync_file); + } + let mut cursor_changed = false; + cursor_changed |= self.cursor_enabled.replace(c.cursor_enabled) != c.cursor_enabled; + cursor_changed |= c.cursor_swap_buffer; + cursor_changed |= self.cursor_x.replace(c.cursor_x) != c.cursor_x; + cursor_changed |= self.cursor_y.replace(c.cursor_y) != c.cursor_y; + if cursor_changed { + self.cursor_changed.set(true); + } + } + + pub fn present( + &self, + node: &Rc, + try_direct_scanout: bool, + ) -> Result<(), MetalError> { let crtc = match self.crtc.get() { Some(crtc) => crtc, _ => return Ok(()), }; - if (!self.has_damage.get() && !self.cursor_scheduled.get()) || !self.can_present.get() { + if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() { return Ok(()); } if !crtc.active.value.get() { @@ -967,9 +965,6 @@ impl MetalConnector { Some(b) => b, _ => return Ok(()), }; - let Some(node) = self.state.root.outputs.get(&self.connector_id) else { - return Ok(()); - }; let cursor = self.cursor_plane.get(); let mut new_fb = None; let mut changes = self.master.change(); @@ -1097,7 +1092,7 @@ impl MetalConnector { } if let Some(fb) = &new_fb { if let Some(dsd) = &fb.direct_scanout_data { - if self.present(false).is_ok() { + if self.present(node, false).is_ok() { let mut cache = self.scanout_buffers.borrow_mut(); if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { cache.insert( @@ -1130,7 +1125,6 @@ impl MetalConnector { apply_change!(plane.crtc_y); apply_change!(plane.crtc_w); apply_change!(plane.crtc_h); - node.schedule.presented(); self.perform_screencopies(&new_fb, &node); if let Some(fb) = new_fb { if fb.direct_scanout_data.is_none() { @@ -1146,7 +1140,6 @@ impl MetalConnector { self.can_present.set(false); self.has_damage.set(false); self.cursor_changed.set(false); - self.cursor_scheduled.set(false); Ok(()) } } @@ -1609,7 +1602,6 @@ fn create_connector( 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), @@ -1617,7 +1609,7 @@ fn create_connector( display: RefCell::new(display), frontend_state: Cell::new(FrontState::Disconnected), cursor_changed: Cell::new(false), - cursor_scheduled: Cell::new(false), + cursor_damage: Cell::new(false), cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), cursor_sync_file: Default::default(), @@ -2378,7 +2370,6 @@ impl MetalBackend { connector.can_present.set(true); connector.has_damage.set(true); connector.cursor_changed.set(true); - connector.cursor_scheduled.set(true); } if dev.unprocessed_change.get() { return self.handle_drm_change_(dev, false); @@ -2439,7 +2430,10 @@ impl MetalBackend { if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); } - if connector.has_damage.get() || connector.cursor_scheduled.get() { + if connector.has_damage.get() + || connector.cursor_damage.get() + || connector.cursor_changed.get() + { connector.schedule_present(); } let dd = connector.display.borrow_mut(); @@ -3185,7 +3179,6 @@ impl MetalBackend { } connector.has_damage.set(true); connector.cursor_changed.set(true); - connector.cursor_scheduled.set(true); connector.schedule_present(); } } diff --git a/src/cursor.rs b/src/cursor.rs index 8f8d1775..63d14ed5 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -166,6 +166,7 @@ impl ServerCursors { let load = |names: &[&str]| ServerCursorTemplate::load(names, theme, &scales, &sizes, &paths, ctx); Ok(Some(Self { + // default: load(&["wait", "watch"])?, default: load(&["default", "left_ptr"])?, context_menu: load(&["context-menu"])?, help: load(&["help"])?, diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 46e407b8..44313422 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -1,5 +1,6 @@ use { crate::{ + backend::HardwareCursorUpdate, cursor::{Cursor, KnownCursor, DEFAULT_CURSOR_SIZE}, fixed::Fixed, rect::Rect, @@ -103,7 +104,7 @@ impl CursorUserGroup { fn remove_hardware_cursor(&self) { self.state.hardware_tick_cursor.push(None); - self.state.disable_hardware_cursors(); + self.state.damage_hardware_cursors(false); self.state.cursor_user_group_hardware_cursor.take(); } @@ -234,6 +235,18 @@ impl CursorUserGroup { } } } + + pub fn present_hardware_cursor( + &self, + output: &Rc, + hc: &mut dyn HardwareCursorUpdate, + ) { + let Some(active) = self.active.get() else { + hc.set_enabled(false); + return; + }; + active.present_hardware_cursor(output, hc); + } } impl CursorUser { @@ -427,86 +440,81 @@ impl CursorUser { return; } let cursor = self.cursor.get(); - self.group.state.hardware_tick_cursor.push(cursor.clone()); - let cursor = match cursor { - Some(c) => c, - _ => { - self.group.state.disable_hardware_cursors(); - return; + self.group.state.hardware_tick_cursor.push(cursor); + for output in self.group.state.root.outputs.lock().values() { + if let Some(hc) = output.hardware_cursor.get() { + if render { + output.hardware_cursor_needs_render.set(true); + } + let defer = output.schedule.defer_cursor_updates(); + if defer { + output.schedule.hardware_cursor_changed(); + } else { + hc.damage(); + } } + } + } + + fn present_hardware_cursor(&self, output: &Rc, hc: &mut dyn HardwareCursorUpdate) { + let Some(cursor) = self.cursor.get() else { + hc.set_enabled(false); + return; }; + let (x, y) = self.pos.get(); + let transform = output.global.persistent.transform.get(); + let render = output.hardware_cursor_needs_render.take(); + let scale = output.global.persistent.scale.get(); if render { cursor.tick(); } - let (x, y) = self.pos.get(); - for output in self.group.state.root.outputs.lock().values() { - if let Some(hc) = output.hardware_cursor.get() { - let commit = || { - let defer = output.schedule.defer_cursor_updates(); - hc.commit(!defer); - if defer { - output.schedule.hardware_cursor_changed(); - } - }; - let transform = output.global.persistent.transform.get(); - let render = render | output.hardware_cursor_needs_render.take(); - let scale = output.global.persistent.scale.get(); - let extents = cursor.extents_at_scale(scale); - let (hc_width, hc_height) = hc.size(); - if render { - let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); - if extents.width() > max_width || extents.height() > max_height { - hc.set_enabled(false); - 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 (width, height) = output.global.pixel_size(); - if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { - if render { - let buffer = hc.get_buffer(); - let res = buffer.render_hardware_cursor( - cursor.deref(), - &self.group.state, - scale, - transform, - ); - match res { - Ok(sync_file) => { - hc.set_sync_file(sync_file); - hc.swap_buffer(); - } - Err(e) => { - log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); - } - } - } - hc.set_enabled(true); - let mode = output.global.mode.get(); - let (x_rel, y_rel) = - transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); - let (hot_x, hot_y) = - transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); - hc.set_position(x_rel - hot_x, y_rel - hot_y); - } else { - if render { - output.hardware_cursor_needs_render.set(true); - } - hc.set_enabled(false); - } - commit(); + let extents = cursor.extents_at_scale(scale); + let (hc_width, hc_height) = hc.size(); + if render { + let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); + if extents.width() > max_width || extents.height() > max_height { + hc.set_enabled(false); + return; } } + 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 (width, height) = output.global.pixel_size(); + if !extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { + if render { + output.hardware_cursor_needs_render.set(true); + } + hc.set_enabled(false); + return; + } + if render { + let buffer = hc.get_buffer(); + let res = + buffer.render_hardware_cursor(cursor.deref(), &self.group.state, scale, transform); + match res { + Ok(sync_file) => { + hc.set_sync_file(sync_file); + hc.swap_buffer(); + } + Err(e) => { + log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); + } + } + } + hc.set_enabled(true); + let mode = output.global.mode.get(); + let (x_rel, y_rel) = transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); + let (hot_x, hot_y) = + transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); + hc.set_position(x_rel - hot_x, y_rel - hot_y); } fn reload_known_cursor(&self) { diff --git a/src/output_schedule.rs b/src/output_schedule.rs index 794d9ccd..bb6e9da4 100644 --- a/src/output_schedule.rs +++ b/src/output_schedule.rs @@ -87,7 +87,7 @@ impl OutputSchedule { } } - pub fn presented(&self) { + pub fn latched(&self) { self.last_present_nsec.set(self.eng.now().nsec()); self.present_scheduled.set(false); self.iteration.fetch_add(1); @@ -166,9 +166,8 @@ impl OutputSchedule { } if self.needs_hardware_cursor_commit.take() { if let Some(hc) = self.hardware_cursor.get() { - if hc.schedule_present() { - self.present_scheduled.set(true); - } + hc.damage(); + self.present_scheduled.set(true); } } if self.needs_software_cursor_damage.take() { diff --git a/src/state.rs b/src/state.rs index d06889be..e5669e04 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,8 +4,8 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, backend::{ Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, - DrmDeviceId, DrmDeviceIds, InputDevice, InputDeviceGroupIds, InputDeviceId, - InputDeviceIds, MonitorInfo, + DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, + InputDeviceId, InputDeviceIds, MonitorInfo, }, backends::dummy::DummyBackend, cli::RunArgs, @@ -847,11 +847,13 @@ impl State { self.slow_ei_clients.clear(); } - pub fn disable_hardware_cursors(&self) { + pub fn damage_hardware_cursors(&self, render: bool) { for output in self.root.outputs.lock().values() { if let Some(hc) = output.hardware_cursor.get() { - hc.set_enabled(false); - hc.commit(true); + if render { + output.hardware_cursor_needs_render.set(true); + } + hc.damage(); } } } @@ -863,7 +865,19 @@ impl State { return; } } - self.disable_hardware_cursors() + self.damage_hardware_cursors(false) + } + + pub fn present_hardware_cursor( + &self, + output: &Rc, + hc: &mut dyn HardwareCursorUpdate, + ) { + let Some(g) = self.cursor_user_group_hardware_cursor.get() else { + hc.set_enabled(false); + return; + }; + g.present_hardware_cursor(output, hc); } pub fn for_each_seat_tester(&self, f: F) { diff --git a/src/tasks/hardware_cursor.rs b/src/tasks/hardware_cursor.rs index 0f6dba81..89b748df 100644 --- a/src/tasks/hardware_cursor.rs +++ b/src/tasks/hardware_cursor.rs @@ -29,7 +29,8 @@ pub async fn handle_hardware_cursor_tick(state: Rc) { break; } } - state.refresh_hardware_cursors(); + cursor.tick(); + state.damage_hardware_cursors(true); } } }