diff --git a/Cargo.lock b/Cargo.lock index 0ebf8e26..1e534143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,7 @@ dependencies = [ "jay-layout-animation", "jay-libinput", "jay-logger", + "jay-output-schedule", "jay-pango", "jay-pr-caps", "jay-sighand", @@ -937,6 +938,18 @@ dependencies = [ "uapi", ] +[[package]] +name = "jay-output-schedule" +version = "0.1.0" +dependencies = [ + "futures-util", + "jay-async-engine", + "jay-io-uring", + "jay-utils", + "log", + "num-traits", +] + [[package]] name = "jay-pango" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 456f55be..6310bbb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ members = [ "theme", "clientmem", "allocator", + "output-schedule", "pango", "libinput", "toml-config", @@ -97,6 +98,7 @@ jay-gfx-types = { version = "0.1.0", path = "gfx-types" } jay-theme = { version = "0.1.0", path = "theme" } jay-clientmem = { version = "0.1.0", path = "clientmem" } jay-allocator = { version = "0.1.0", path = "allocator" } +jay-output-schedule = { version = "0.1.0", path = "output-schedule" } jay-pango = { version = "0.1.0", path = "pango" } jay-libinput = { version = "0.1.0", path = "libinput" } diff --git a/output-schedule/Cargo.toml b/output-schedule/Cargo.toml new file mode 100644 index 00000000..d5f20ba8 --- /dev/null +++ b/output-schedule/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-output-schedule" +version = "0.1.0" +edition = "2024" +license = "GPL-3.0-only" + +[dependencies] +jay-async-engine = { version = "0.1.0", path = "../async-engine" } +jay-io-uring = { version = "0.1.0", path = "../io-uring" } +jay-utils = { version = "0.1.0", path = "../utils" } + +futures-util = "0.3.30" +log = "0.4.20" +num-traits = "0.2.17" diff --git a/output-schedule/src/lib.rs b/output-schedule/src/lib.rs new file mode 100644 index 00000000..69933f2b --- /dev/null +++ b/output-schedule/src/lib.rs @@ -0,0 +1,219 @@ +use { + jay_async_engine::AsyncEngine, + jay_io_uring::{IoUring, IoUringError}, + jay_utils::{ + asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt, + numcell::NumCell, + }, + futures_util::{FutureExt, select}, + num_traits::ToPrimitive, + std::{cell::Cell, rc::Rc}, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum Change { + /// The backend has applied the latest changes. + None, + /// There are changes that the backend is not yet aware of. + Scheduled, + /// The backend is aware that there are changes and will apply them as part of the + /// next latch event. + AwaitingLatch, +} + +pub struct OutputSchedule { + changed: AsyncEvent, + run: Cell, + + damage_connector: Rc, + hardware_cursor_damage: CloneCell>>, + cursor_hz_changed: Rc)>, + + persistent: Rc, + + last_present_nsec: Cell, + cursor_delta_nsec: Cell>, + + ring: Rc, + eng: Rc, + + vrr_enabled: Cell, + + hardware_cursor_change: Cell, + software_cursor_change: Cell, + + iteration: NumCell, +} + +pub trait OutputSchedulePersistent { + fn vrr_cursor_hz(&self) -> Option; + fn set_vrr_cursor_hz(&self, hz: Option); +} + +impl OutputSchedule { + pub fn new( + ring: Rc, + eng: Rc, + persistent: Rc, + damage_connector: Rc, + cursor_hz_changed: Rc)>, + ) -> Self { + let slf = Self { + changed: Default::default(), + run: Default::default(), + damage_connector, + cursor_hz_changed, + ring, + eng, + vrr_enabled: Default::default(), + hardware_cursor_change: Cell::new(Change::None), + software_cursor_change: Cell::new(Change::None), + hardware_cursor_damage: Default::default(), + persistent: persistent.clone(), + last_present_nsec: Default::default(), + cursor_delta_nsec: Default::default(), + iteration: Default::default(), + }; + if let Some(hz) = persistent.vrr_cursor_hz() { + slf.set_cursor_hz(hz); + } + slf + } + + pub async fn drive(self: Rc) { + loop { + self.run_once().await; + while !self.run.take() { + self.changed.triggered().await; + } + } + } + + fn trigger(&self) { + let trigger = self.vrr_enabled.get() + && self.cursor_delta_nsec.is_some() + && (self.software_cursor_change.get() == Change::Scheduled + || self.hardware_cursor_change.get() == Change::Scheduled); + if trigger { + self.run.set(true); + self.changed.trigger(); + } + } + + pub fn latched(&self) { + self.last_present_nsec.set(self.eng.now().nsec()); + if self.software_cursor_change.get() == Change::AwaitingLatch { + self.software_cursor_change.set(Change::None); + } + if self.hardware_cursor_change.get() == Change::AwaitingLatch { + self.hardware_cursor_change.set(Change::None); + } + self.iteration.fetch_add(1); + self.trigger(); + } + + pub fn vrr_enabled(&self) -> bool { + self.vrr_enabled.get() + } + + pub fn set_vrr_enabled(&self, enabled: bool) { + self.vrr_enabled.set(enabled); + self.trigger(); + } + + pub fn set_cursor_hz(&self, hz: f64) { + let (hz, delta) = match map_cursor_hz(hz) { + None => { + log::warn!("Ignoring cursor frequency {hz}"); + return; + } + Some(v) => v, + }; + self.persistent.set_vrr_cursor_hz(hz); + (self.cursor_hz_changed)(hz); + self.cursor_delta_nsec.set(delta); + self.trigger(); + } + + pub fn set_hardware_cursor_damage(&self, damage: &Option>) { + self.hardware_cursor_damage.set(damage.clone()); + } + + pub fn defer_cursor_updates(&self) -> bool { + self.vrr_enabled.get() && self.cursor_delta_nsec.is_some() + } + + pub fn hardware_cursor_changed(&self) { + if self.hardware_cursor_change.get() == Change::None { + self.hardware_cursor_change.set(Change::Scheduled); + self.trigger(); + } + } + + pub fn software_cursor_changed(&self) { + if self.software_cursor_change.get() == Change::None { + self.software_cursor_change.set(Change::Scheduled); + self.trigger(); + } + } + + async fn run_once(&self) { + loop { + if self.hardware_cursor_change.get() != Change::Scheduled + && self.software_cursor_change.get() != Change::Scheduled + { + return; + } + if !self.vrr_enabled.get() { + return; + } + let Some(duration) = self.cursor_delta_nsec.get() else { + return; + }; + let iteration = self.iteration.get(); + let next_present = self.last_present_nsec.get().saturating_add(duration); + let res: Result<(), IoUringError> = select! { + _ = self.changed.triggered().fuse() => continue, + v = self.ring.timeout(next_present).fuse() => v, + }; + if let Err(e) = res { + log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); + return; + } + if iteration == self.iteration.get() { + break; + } + } + self.commit_cursor(); + } + + pub fn commit_cursor(&self) { + if self.hardware_cursor_change.get() == Change::Scheduled { + if let Some(damage) = self.hardware_cursor_damage.get() { + damage(); + } + self.hardware_cursor_change.set(Change::AwaitingLatch); + } + if self.software_cursor_change.get() == Change::Scheduled { + (self.damage_connector)(); + self.software_cursor_change.set(Change::AwaitingLatch); + } + } +} + +pub fn map_cursor_hz(hz: f64) -> Option<(Option, Option)> { + if hz <= 0.0 { + return Some((Some(0.0), Some(u64::MAX))); + } + let delta = (1_000_000_000.0 / hz).to_u64(); + if delta.is_none() { + if hz > 0.0 { + return Some((None, None)); + } + return None; + } + if delta == Some(0) { + return Some((None, None)); + } + Some((Some(hz), delta)) +} diff --git a/src/compositor.rs b/src/compositor.rs index 17db782f..9eac6f98 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -49,7 +49,7 @@ use { kbvm::KbvmContext, leaks, logger::Logger, - output_schedule::OutputSchedule, + output_schedule::create_output_schedule, pr_caps::{PrCapsThread, pr_caps}, scale::Scale, sighand::{self, SighandError}, @@ -713,7 +713,7 @@ fn create_dummy_output(state: &Rc) { head_managers: HeadManagers::new(head_name, head_state), wlr_output_heads: Default::default(), }); - let schedule = Rc::new(OutputSchedule::new( + let schedule = Rc::new(create_output_schedule( state, &connector_data, &persistent_state, diff --git a/src/config/handler.rs b/src/config/handler.rs index ab68c3c9..3cdb785e 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1528,7 +1528,7 @@ impl ConfigProxyHandler { match connector { Some(c) => { let connector = self.get_output_node(c)?; - connector.schedule.set_cursor_hz(&self.state, hz); + connector.schedule.set_cursor_hz(hz); } _ => { let Some((hz, _)) = map_cursor_hz(hz) else { diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 6b79475d..21632dbf 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -474,7 +474,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; - c.schedule.set_cursor_hz(&self.state, req.hz); + c.schedule.set_cursor_hz(req.hz); Ok(()) } diff --git a/src/output_schedule.rs b/src/output_schedule.rs index 098c1e91..f3b4ba88 100644 --- a/src/output_schedule.rs +++ b/src/output_schedule.rs @@ -1,215 +1,35 @@ +pub use jay_output_schedule::*; + use { crate::{ - async_engine::AsyncEngine, - backend::HardwareCursor, ifs::wl_output::PersistentOutputState, - io_uring::{IoUring, IoUringError}, state::{ConnectorData, State}, - utils::{ - asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt, - numcell::NumCell, - }, }, - futures_util::{FutureExt, select}, - num_traits::ToPrimitive, - std::{cell::Cell, rc::Rc}, + std::rc::Rc, }; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum Change { - /// The backend has applied the latest changes. - None, - /// There are changes that the backend is not yet aware of. - Scheduled, - /// The backend is aware that there are changes and will apply them as part of the - /// next latch event. - AwaitingLatch, -} - -pub struct OutputSchedule { - changed: AsyncEvent, - run: Cell, - - connector: Rc, - hardware_cursor: CloneCell>>, - - persistent: Rc, - - last_present_nsec: Cell, - cursor_delta_nsec: Cell>, - - ring: Rc, - eng: Rc, - - vrr_enabled: Cell, - - hardware_cursor_change: Cell, - software_cursor_change: Cell, - - iteration: NumCell, -} - -impl OutputSchedule { - pub fn new( - state: &State, - connector: &Rc, - persistent: &Rc, - ) -> Self { - let slf = Self { - changed: Default::default(), - run: Default::default(), - connector: connector.clone(), - ring: state.ring.clone(), - eng: state.eng.clone(), - vrr_enabled: Default::default(), - hardware_cursor_change: Cell::new(Change::None), - software_cursor_change: Cell::new(Change::None), - hardware_cursor: Default::default(), - persistent: persistent.clone(), - last_present_nsec: Default::default(), - cursor_delta_nsec: Default::default(), - iteration: Default::default(), - }; - if let Some(hz) = persistent.vrr_cursor_hz.get() { - slf.set_cursor_hz(state, hz); - } - slf +impl OutputSchedulePersistent for PersistentOutputState { + fn vrr_cursor_hz(&self) -> Option { + self.vrr_cursor_hz.get() } - pub async fn drive(self: Rc) { - loop { - self.run_once().await; - while !self.run.take() { - self.changed.triggered().await; - } - } - } - - fn trigger(&self) { - let trigger = self.vrr_enabled.get() - && self.cursor_delta_nsec.is_some() - && (self.software_cursor_change.get() == Change::Scheduled - || self.hardware_cursor_change.get() == Change::Scheduled); - if trigger { - self.run.set(true); - self.changed.trigger(); - } - } - - pub fn latched(&self) { - self.last_present_nsec.set(self.eng.now().nsec()); - if self.software_cursor_change.get() == Change::AwaitingLatch { - self.software_cursor_change.set(Change::None); - } - if self.hardware_cursor_change.get() == Change::AwaitingLatch { - self.hardware_cursor_change.set(Change::None); - } - self.iteration.fetch_add(1); - self.trigger(); - } - - pub fn vrr_enabled(&self) -> bool { - self.vrr_enabled.get() - } - - pub fn set_vrr_enabled(&self, enabled: bool) { - self.vrr_enabled.set(enabled); - self.trigger(); - } - - pub fn set_cursor_hz(&self, _state: &State, hz: f64) { - let (hz, delta) = match map_cursor_hz(hz) { - None => { - log::warn!("Ignoring cursor frequency {hz}"); - return; - } - Some(v) => v, - }; - self.persistent.vrr_cursor_hz.set(hz); - self.connector.head_managers.handle_cursor_hz_change(hz); - self.cursor_delta_nsec.set(delta); - self.trigger(); - } - - pub fn set_hardware_cursor(&self, hc: &Option>) { - self.hardware_cursor.set(hc.clone()); - } - - pub fn defer_cursor_updates(&self) -> bool { - self.vrr_enabled.get() && self.cursor_delta_nsec.is_some() - } - - pub fn hardware_cursor_changed(&self) { - if self.hardware_cursor_change.get() == Change::None { - self.hardware_cursor_change.set(Change::Scheduled); - self.trigger(); - } - } - - pub fn software_cursor_changed(&self) { - if self.software_cursor_change.get() == Change::None { - self.software_cursor_change.set(Change::Scheduled); - self.trigger(); - } - } - - async fn run_once(&self) { - loop { - if self.hardware_cursor_change.get() != Change::Scheduled - && self.software_cursor_change.get() != Change::Scheduled - { - return; - } - if !self.vrr_enabled.get() { - return; - } - let Some(duration) = self.cursor_delta_nsec.get() else { - return; - }; - let iteration = self.iteration.get(); - let next_present = self.last_present_nsec.get().saturating_add(duration); - let res: Result<(), IoUringError> = select! { - _ = self.changed.triggered().fuse() => continue, - v = self.ring.timeout(next_present).fuse() => v, - }; - if let Err(e) = res { - log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); - return; - } - if iteration == self.iteration.get() { - break; - } - } - self.commit_cursor(); - } - - pub fn commit_cursor(&self) { - if self.hardware_cursor_change.get() == Change::Scheduled { - if let Some(hc) = self.hardware_cursor.get() { - hc.damage(); - } - self.hardware_cursor_change.set(Change::AwaitingLatch); - } - if self.software_cursor_change.get() == Change::Scheduled { - self.connector.damage(); - self.software_cursor_change.set(Change::AwaitingLatch); - } + fn set_vrr_cursor_hz(&self, hz: Option) { + self.vrr_cursor_hz.set(hz); } } -pub fn map_cursor_hz(hz: f64) -> Option<(Option, Option)> { - if hz <= 0.0 { - return Some((Some(0.0), Some(u64::MAX))); - } - let delta = (1_000_000_000.0 / hz).to_u64(); - if delta.is_none() { - if hz > 0.0 { - return Some((None, None)); - } - return None; - } - if delta == Some(0) { - return Some((None, None)); - } - Some((Some(hz), delta)) +pub fn create_output_schedule( + state: &State, + connector: &Rc, + persistent: &Rc, +) -> OutputSchedule { + let damage_connector = connector.clone(); + let cursor_hz_connector = connector.clone(); + OutputSchedule::new( + state.ring.clone(), + state.eng.clone(), + persistent.clone(), + Rc::new(move || damage_connector.damage()), + Rc::new(move |hz| cursor_hz_connector.head_managers.handle_cursor_hz_change(hz)), + ) } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index aef06d91..16362e44 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -7,7 +7,7 @@ use { jay_tray_v1::JayTrayV1Global, wl_output::{BlendSpace, WlOutputGlobal}, }, - output_schedule::OutputSchedule, + output_schedule::create_output_schedule, state::{ConnectorData, OutputData, State}, tree::{OutputNode, Transform, WsMoveConfig, move_ws_to_output}, utils::{ @@ -177,7 +177,11 @@ impl ConnectorHandler { info.primaries, info.luminance, )); - let schedule = Rc::new(OutputSchedule::new(&self.state, &self.data, &desired_state)); + let schedule = Rc::new(create_output_schedule( + &self.state, + &self.data, + &desired_state, + )); let _schedule = self .state .eng @@ -309,7 +313,12 @@ impl ConnectorHandler { match event { ConnectorEvent::Disconnected => break 'outer, ConnectorEvent::HardwareCursor(hc) => { - on.schedule.set_hardware_cursor(&hc); + let hardware_cursor_damage = hc.as_ref().map(|hc| { + let hc = hc.clone(); + Rc::new(move || hc.damage()) as Rc + }); + on.schedule + .set_hardware_cursor_damage(&hardware_cursor_damage); on.set_hardware_cursor(hc); self.state.refresh_hardware_cursors(); }