diff --git a/docs/features.md b/docs/features.md index 7b0fb94e..12605549 100644 --- a/docs/features.md +++ b/docs/features.md @@ -126,6 +126,10 @@ Jay supports leasing VR headsets to applications. Jay supports adaptive sync with configurable cursor refresh rates. +## Tearing + +Jay supports tearing presentation for games. + ## Protocol Support Jay supports the following wayland protocols: @@ -153,7 +157,7 @@ Jay supports the following wayland protocols: | wp_presentation | 1 | | | wp_security_context_manager_v1 | 1 | | | wp_single_pixel_buffer_manager_v1 | 1 | | -| wp_tearing_control_manager_v1 | 1[^no_tearing] | | +| wp_tearing_control_manager_v1 | 1 | | | wp_viewporter | 1 | | | xdg_activation_v1 | 1 | | | xdg_toplevel_drag_manager_v1 | 1 | | @@ -176,7 +180,6 @@ Jay supports the following wayland protocols: | zxdg_output_manager_v1 | 3 | | [^no_touch]: Touch input is not supported. -[^no_tearing]: Tearing screen updates are not supported. [^lsaccess]: Sandboxes can restrict access to this protocol. [^ts_rejected]: Seat creation is always rejected. @@ -185,4 +188,3 @@ Jay supports the following wayland protocols: The following features are currently not supported but might get implemented in the future: - Touch support. -- Tearing updates of fullscreen games. diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index a4faee8e..8b7f9115 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -25,7 +25,7 @@ use { timer::Timer, video::{ connector_type::{ConnectorType, CON_UNKNOWN}, - Connector, DrmDevice, GfxApi, Mode, Transform, VrrMode, + Connector, DrmDevice, GfxApi, Mode, TearingMode, Transform, VrrMode, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, @@ -808,6 +808,10 @@ impl Client { self.send(&ClientMessage::SetVrrCursorHz { connector, hz }) } + pub fn set_tearing_mode(&self, connector: Option, mode: TearingMode) { + self.send(&ClientMessage::SetTearingMode { connector, mode }) + } + pub fn drm_devices(&self) -> Vec { let res = self.send_with_response(&ClientMessage::GetDrmDevices); get_response!(res, vec![], GetDrmDevices { devices }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 1cec98ab..573c83d6 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -8,7 +8,10 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable, Color}, timer::Timer, - video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi, Transform, VrrMode}, + video::{ + connector_type::ConnectorType, Connector, DrmDevice, GfxApi, TearingMode, Transform, + VrrMode, + }, Axis, Direction, PciId, Workspace, _private::{PollableId, WireMode}, }, @@ -495,6 +498,10 @@ pub enum ClientMessage<'a> { connector: Option, hz: f64, }, + SetTearingMode { + connector: Option, + mode: TearingMode, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 55859ab1..8a40e2d3 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -262,6 +262,11 @@ impl Connector { pub fn set_vrr_cursor_hz(self, hz: f64) { get!().set_vrr_cursor_hz(Some(self), hz) } + + /// Sets the tearing mode. + pub fn set_tearing_mode(self, mode: TearingMode) { + get!().set_tearing_mode(Some(self), mode) + } } /// Returns all available DRM devices. @@ -580,3 +585,30 @@ pub fn set_vrr_mode(mode: VrrMode) { pub fn set_vrr_cursor_hz(hz: f64) { get!().set_vrr_cursor_hz(None, hz) } + +/// The tearing mode of a connector. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct TearingMode(pub u32); + +impl TearingMode { + /// Tearing is never enabled. + pub const NEVER: Self = Self(0); + /// Tearing is always enabled. + pub const ALWAYS: Self = Self(1); + /// Tearing is enabled when one or more applications are displayed fullscreen. + pub const VARIANT_1: Self = Self(2); + /// Tearing is enabled when a single application is displayed fullscreen. + pub const VARIANT_2: Self = Self(3); + /// Tearing is enabled when a single application is displayed fullscreen and the + /// application has requested tearing. + /// + /// This is the default. + pub const VARIANT_3: Self = Self(4); +} + +/// Sets the default tearing mode. +/// +/// This setting can be overwritten on a per-connector basis with [Connector::set_tearing_mode]. +pub fn set_tearing_mode(mode: TearingMode) { + get!().set_tearing_mode(None, mode) +} diff --git a/release-notes.md b/release-notes.md index e2187e4c..0ec87cae 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,7 @@ - Add fine-grained damage tracking. - Add support for adaptive sync. +- Add support for tearing. # 1.4.0 (2024-07-07) diff --git a/src/backend.rs b/src/backend.rs index 3a54fa08..f3e40ff9 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -112,6 +112,9 @@ pub trait Connector { fn set_vrr_enabled(&self, enabled: bool) { let _ = enabled; } + fn set_tearing_enabled(&self, enabled: bool) { + let _ = enabled; + } } #[derive(Debug)] diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index e0471a11..406b01a8 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -33,7 +33,8 @@ use { DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmLease, DrmMaster, DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, DrmVersion, PropBlob, DRM_CLIENT_CAP_ATOMIC, - DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, + DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, + DRM_MODE_PAGE_FLIP_EVENT, }, gbm::{GbmBo, GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT}, Modifier, INVALID_MODIFIER, @@ -89,6 +90,7 @@ pub struct MetalDrmDevice { pub _max_height: u32, pub cursor_width: u64, pub cursor_height: u64, + pub supports_async_commit: bool, pub gbm: GbmDevice, pub handle_events: HandleEvents, pub ctx: CloneCell>, @@ -456,6 +458,8 @@ pub struct MetalConnector { pub active_framebuffer: RefCell>, pub next_framebuffer: OpaqueCell>, pub direct_scanout_active: Cell, + + pub tearing_requested: Cell, } impl Debug for MetalConnector { @@ -947,6 +951,16 @@ impl MetalConnector { let cursor = self.cursor_plane.get(); let mut new_fb = None; let mut changes = self.master.change(); + let mut try_async_flip = self.tearing_requested.get() && self.dev.supports_async_commit; + macro_rules! change { + ($c:expr, $prop:expr, $new:expr) => {{ + if $prop.value.get() != $new { + $c.change($prop.id, $new as u64); + try_async_flip = false; + $prop.pending_value.set(Some($new)); + } + }}; + } if self.has_damage.get() { if !self.backend.check_render_context(&self.dev) { return Ok(()); @@ -978,13 +992,13 @@ impl MetalConnector { let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1); changes.change_object(plane.id, |c| { c.change(plane.fb_id, fb.fb.id().0 as _); - c.change(plane.src_w.id, (src_width as u64) << 16); - c.change(plane.src_h.id, (src_height as u64) << 16); - c.change(plane.crtc_x.id, crtc_x as u64); - c.change(plane.crtc_y.id, crtc_y as u64); - c.change(plane.crtc_w.id, crtc_w as u64); - c.change(plane.crtc_h.id, crtc_h as u64); - if !self.dev.is_nvidia { + change!(c, plane.src_w, (src_width as u32) << 16); + change!(c, plane.src_h, (src_height as u32) << 16); + change!(c, plane.crtc_x, crtc_x); + change!(c, plane.crtc_y, crtc_y); + change!(c, plane.crtc_w, crtc_w); + change!(c, plane.crtc_h, crtc_h); + if !try_async_flip && !self.dev.is_nvidia { c.change(plane.in_fence_fd, in_fence as u64); } }); @@ -1002,6 +1016,7 @@ impl MetalConnector { let mut cursor_swap_buffer = false; let mut cursor_sync_file = None; if self.cursor_changed.get() && cursor.is_some() { + try_async_flip = false; let plane = cursor.unwrap(); if self.cursor_enabled.get() { cursor_swap_buffer = self.cursor_swap_buffer.get(); @@ -1039,7 +1054,18 @@ impl MetalConnector { }); } } - if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) { + let mut res; + 'commit: { + const FLAGS: u32 = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + if try_async_flip { + res = changes.commit(FLAGS | DRM_MODE_PAGE_FLIP_ASYNC, 0); + if res.is_ok() { + break 'commit; + } + } + res = changes.commit(FLAGS, 0); + } + if let Err(e) = res { if let DrmError::Atomic(OsError(c::EACCES)) = e { log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); self.render_result @@ -1069,6 +1095,19 @@ impl MetalConnector { .discard_presentation_feedback(); Err(MetalError::Commit(e)) } else { + macro_rules! apply_change { + ($prop:expr) => { + if let Some(v) = $prop.pending_value.take() { + $prop.value.set(v); + } + }; + } + apply_change!(plane.src_w); + apply_change!(plane.src_h); + apply_change!(plane.crtc_x); + 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 { @@ -1374,6 +1413,19 @@ impl Connector for MetalConnector { crtc.vrr_enabled.value.set(new_enabled); self.send_vrr_enabled(); } + + fn set_tearing_enabled(&self, enabled: bool) { + if !self.dev.supports_async_commit { + return; + } + if self.tearing_requested.replace(enabled) != enabled { + let msg = match enabled { + true => "Enabling", + false => "Disabling", + }; + log::debug!("{msg} tearing on output {}", self.kernel_id()); + } + } } pub struct MetalCrtc { @@ -1524,6 +1576,7 @@ fn create_connector( next_framebuffer: Default::default(), direct_scanout_active: Cell::new(false), next_flip_nsec: Cell::new(0), + tearing_requested: Cell::new(false), }); let futures = ConnectorFutures { _present: backend @@ -1810,6 +1863,7 @@ impl CollectedProperties { Some((def, value)) => Ok(MutableProperty { id: def.id, value: Cell::new(*value), + pending_value: Cell::new(None), }), _ => Err(DrmError::MissingProperty(name.to_string().into_boxed_str())), } @@ -1820,6 +1874,7 @@ impl CollectedProperties { pub struct MutableProperty { pub id: DrmProperty, pub value: Cell, + pub pending_value: Cell>, } impl MutableProperty { @@ -1830,6 +1885,7 @@ impl MutableProperty { MutableProperty { id: self.id, value: Cell::new(f(self.value.into_inner())), + pending_value: Cell::new(None), } } } @@ -1987,6 +2043,7 @@ impl MetalBackend { disconnect |= !old.is_same_monitor(&dd); } if disconnect { + c.tearing_requested.set(false); if let Some(lease_id) = c.lease.get() { if let Some(lease) = dev.dev.leases.remove(&lease_id) { if !lease.try_revoke() { @@ -2152,6 +2209,7 @@ impl MetalBackend { _max_height: resources.max_height, cursor_width, cursor_height, + supports_async_commit: master.supports_async_commit(), gbm, handle_events: HandleEvents { handle_events: Cell::new(None), diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 80816085..8de02db2 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -8,7 +8,7 @@ use { }, clap::{Args, Subcommand, ValueEnum}, isnt::std_1::vec::IsntVecExt, - jay_config::video::{Transform, VrrMode}, + jay_config::video::{TearingMode, Transform, VrrMode}, std::{ cell::RefCell, fmt::{Display, Formatter}, @@ -120,6 +120,8 @@ pub enum OutputCommand { NonDesktop(NonDesktopArgs), /// Change VRR settings. Vrr(VrrArgs), + /// Change tearing settings. + Tearing(TearingArgs), } #[derive(ValueEnum, Debug, Clone)] @@ -144,13 +146,13 @@ pub struct VrrArgs { #[derive(Subcommand, Debug, Clone)] pub enum VrrCommand { /// Sets the mode that determines when VRR is enabled. - SetMode(SetModeArgs), + SetMode(SetVrrModeArgs), /// Sets the maximum refresh rate of the cursor. SetCursorHz(CursorHzArgs), } #[derive(Args, Debug, Clone)] -pub struct SetModeArgs { +pub struct SetVrrModeArgs { #[clap(value_enum)] pub mode: VrrModeArg, } @@ -175,6 +177,41 @@ pub struct CursorHzArgs { pub rate: String, } +#[derive(Args, Debug, Clone)] +pub struct TearingArgs { + #[clap(subcommand)] + pub command: TearingCommand, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum TearingCommand { + /// Sets the mode that determines when tearing is enabled. + SetMode(SetTearingModeArgs), +} + +#[derive(Args, Debug, Clone)] +pub struct SetTearingModeArgs { + #[clap(value_enum)] + pub mode: TearingModeArg, +} + +#[derive(ValueEnum, Debug, Copy, Clone, Hash, PartialEq)] +pub enum TearingModeArg { + /// Tearing is never enabled. + Never, + /// Tearing is always enabled. + Always, + /// Tearing is enabled when one or more applications are displayed fullscreen. + Variant1, + /// Tearing is enabled when a single application is displayed fullscreen. + Variant2, + /// Tearing is enabled when a single application is displayed fullscreen and the + /// application has requested tearing. + /// + /// This is the default. + Variant3, +} + #[derive(Args, Debug, Clone)] pub struct PositionArgs { /// The top-left x coordinate. @@ -280,6 +317,7 @@ struct Output { pub vrr_enabled: bool, pub vrr_mode: VrrMode, pub vrr_cursor_hz: Option, + pub tearing_mode: TearingMode, } #[derive(Copy, Clone, Debug)] @@ -487,6 +525,27 @@ impl Randr { } } } + OutputCommand::Tearing(a) => { + self.handle_error(randr, move |msg| { + eprintln!("Could not change the tearing setting: {}", msg); + }); + match a.command { + TearingCommand::SetMode(a) => { + let mode = match a.mode { + TearingModeArg::Never => VrrMode::NEVER, + TearingModeArg::Always => VrrMode::ALWAYS, + TearingModeArg::Variant1 => VrrMode::VARIANT_1, + TearingModeArg::Variant2 => VrrMode::VARIANT_2, + TearingModeArg::Variant3 => VrrMode::VARIANT_3, + }; + tc.send(jay_randr::SetTearingMode { + self_id: randr, + output: &args.output, + mode: mode.0, + }); + } + } + } } tc.round_trip().await; } @@ -621,6 +680,21 @@ impl Randr { println!(" VRR cursor hz: {}", hz); } } + { + let mode_str; + let mode = match o.tearing_mode { + TearingMode::NEVER => "never", + TearingMode::ALWAYS => "always", + TearingMode::VARIANT_1 => "variant1", + TearingMode::VARIANT_2 => "variant2", + TearingMode::VARIANT_3 => "variant3", + _ => { + mode_str = format!("unknown ({})", o.vrr_mode.0); + &mode_str + } + }; + println!(" Tearing mode: {}", mode); + } println!(" position: {} x {}", o.x, o.y); println!(" logical size: {} x {}", o.width, o.height); if let Some(mode) = &o.current_mode { @@ -713,6 +787,7 @@ impl Randr { vrr_enabled: false, vrr_mode: VrrMode::NEVER, vrr_cursor_hz: None, + tearing_mode: TearingMode::NEVER, }); }); jay_randr::NonDesktopOutput::handle(tc, randr, data.clone(), |data, msg| { @@ -737,6 +812,7 @@ impl Randr { vrr_enabled: false, vrr_mode: VrrMode::NEVER, vrr_cursor_hz: None, + tearing_mode: TearingMode::NEVER, }); }); jay_randr::VrrState::handle(tc, randr, data.clone(), |data, msg| { @@ -753,6 +829,12 @@ impl Randr { let output = c.output.as_mut().unwrap(); output.vrr_cursor_hz = Some(msg.hz); }); + jay_randr::TearingState::handle(tc, randr, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + let c = data.connectors.last_mut().unwrap(); + let output = c.output.as_mut().unwrap(); + output.tearing_mode = TearingMode(msg.mode); + }); jay_randr::Mode::handle(tc, randr, data.clone(), |data, msg| { let mut data = data.borrow_mut(); let c = data.connectors.last_mut().unwrap(); diff --git a/src/compositor.rs b/src/compositor.rs index 0d9d431e..ab335892 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -33,7 +33,8 @@ use { tasks::{self, idle}, tree::{ container_layout, container_render_data, float_layout, float_titles, - output_render_data, DisplayNode, NodeIds, OutputNode, VrrMode, WorkspaceNode, + output_render_data, DisplayNode, NodeIds, OutputNode, TearingMode, VrrMode, + WorkspaceNode, }, user_session::import_environment, utils::{ @@ -249,6 +250,7 @@ fn start_compositor2( damage_visualizer: DamageVisualizer::new(&engine), default_vrr_mode: Cell::new(VrrMode::NEVER), default_vrr_cursor_hz: Cell::new(None), + default_tearing_mode: Cell::new(TearingMode::VARIANT_3), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -425,6 +427,7 @@ fn create_dummy_output(state: &Rc) { pos: Default::default(), vrr_mode: Cell::new(VrrMode::NEVER), vrr_cursor_hz: Default::default(), + tearing_mode: Cell::new(&TearingMode::Never), }); let connector = Rc::new(DummyOutput { id: state.connector_ids.next(), diff --git a/src/config/handler.rs b/src/config/handler.rs index 5a06d9a3..2e471910 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -15,7 +15,7 @@ use { theme::{Color, ThemeSized, DEFAULT_FONT}, tree::{ move_ws_to_output, ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, - OutputNode, VrrMode, WsMoveConfig, + OutputNode, TearingMode, VrrMode, WsMoveConfig, }, utils::{ asyncevent::AsyncEvent, @@ -48,7 +48,10 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, - video::{Connector, DrmDevice, GfxApi, Transform, VrrMode as ConfigVrrMode}, + video::{ + Connector, DrmDevice, GfxApi, TearingMode as ConfigTearingMode, Transform, + VrrMode as ConfigVrrMode, + }, Axis, Direction, Workspace, }, libloading::Library, @@ -1045,7 +1048,7 @@ impl ConfigProxyHandler { Some(c) => { let connector = self.get_output_node(c)?; connector.global.persistent.vrr_mode.set(mode); - connector.update_vrr_state(); + connector.update_presentation_type(); } _ => self.state.default_vrr_mode.set(mode), } @@ -1072,6 +1075,25 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_tearing_mode( + &self, + connector: Option, + mode: ConfigTearingMode, + ) -> Result<(), CphError> { + let Some(mode) = TearingMode::from_config(mode) else { + return Err(CphError::UnknownTearingMode(mode)); + }; + match connector { + Some(c) => { + let connector = self.get_output_node(c)?; + connector.global.persistent.tearing_mode.set(mode); + connector.update_presentation_type(); + } + _ => self.state.default_tearing_mode.set(mode), + } + Ok(()) + } + fn handle_connector_set_transform( &self, connector: Connector, @@ -1872,6 +1894,9 @@ impl ConfigProxyHandler { ClientMessage::SetVrrCursorHz { connector, hz } => self .handle_set_vrr_cursor_hz(connector, hz) .wrn("set_vrr_cursor_hz")?, + ClientMessage::SetTearingMode { connector, mode } => self + .handle_set_tearing_mode(connector, mode) + .wrn("set_tearing_mode")?, } Ok(()) } @@ -1937,6 +1962,8 @@ enum CphError { UnknownVrrMode(ConfigVrrMode), #[error("Invalid cursor hz {0}")] InvalidCursorHz(f64), + #[error("Unknown tearing mode {0:?}")] + UnknownTearingMode(ConfigTearingMode), } trait WithRequestName { diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index d8057530..bebbf4a4 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -66,7 +66,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 2 + 3 } fn required_caps(&self) -> ClientCaps { diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 50b87597..76fffcd9 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -7,11 +7,13 @@ use { object::{Object, Version}, scale::Scale, state::{ConnectorData, DrmDevData, OutputData}, - tree::{OutputNode, VrrMode}, + tree::{OutputNode, TearingMode, VrrMode}, utils::{gfx_api_ext::GfxApiExt, transform_ext::TransformExt}, wire::{jay_randr::*, JayRandrId}, }, - jay_config::video::{GfxApi, Transform, VrrMode as ConfigVrrMode}, + jay_config::video::{ + GfxApi, TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode, + }, std::rc::Rc, thiserror::Error, }; @@ -24,6 +26,7 @@ pub struct JayRandr { } const VRR_CAPABLE_SINCE: Version = Version(2); +const TEARING_SINCE: Version = Version(3); impl JayRandr { pub fn new(id: JayRandrId, client: &Rc, version: Version) -> Self { @@ -116,6 +119,12 @@ impl JayRandr { }); } } + if self.version >= TEARING_SINCE { + self.client.event(TearingState { + self_id: self.id, + mode: node.global.persistent.tearing_mode.get().to_config().0, + }); + } let current_mode = global.mode.get(); for mode in &global.modes { self.client.event(Mode { @@ -325,7 +334,7 @@ impl JayRandrRequestHandler for JayRandr { return Ok(()); }; c.global.persistent.vrr_mode.set(mode); - c.update_vrr_state(); + c.update_presentation_type(); return Ok(()); } @@ -340,6 +349,22 @@ impl JayRandrRequestHandler for JayRandr { c.schedule.set_cursor_hz(req.hz); Ok(()) } + + fn set_tearing_mode( + &self, + req: SetTearingMode<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let Some(mode) = TearingMode::from_config(ConfigTearingMode(req.mode)) else { + return Err(JayRandrError::UnknownTearingMode(req.mode)); + }; + let Some(c) = self.get_output_node(req.output) else { + return Ok(()); + }; + c.global.persistent.tearing_mode.set(mode); + c.update_presentation_type(); + return Ok(()); + } } object_base! { @@ -357,5 +382,7 @@ pub enum JayRandrError { ClientError(Box), #[error("Unknown VRR mode {0}")] UnknownVrrMode(u32), + #[error("Unknown tearing mode {0}")] + UnknownTearingMode(u32), } efrom!(JayRandrError, ClientError); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index d72a9697..74069f02 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -10,7 +10,7 @@ use { object::{Object, Version}, rect::Rect, state::{ConnectorData, State}, - tree::{calculate_logical_size, OutputNode, VrrMode}, + tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode}, utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, transform_ext::TransformExt}, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, }, @@ -93,6 +93,7 @@ pub struct PersistentOutputState { pub pos: Cell<(i32, i32)>, pub vrr_mode: Cell<&'static VrrMode>, pub vrr_cursor_hz: Cell>, + pub tearing_mode: Cell<&'static TearingMode>, } #[derive(Eq, PartialEq, Hash)] diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 59b12a74..77dd8165 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -287,7 +287,7 @@ pub struct WlSurface { pub constraints: SmallMap, 1>, xwayland_serial: Cell>, tearing_control: CloneCell>>, - tearing: Cell, + pub tearing: Cell, version: Version, pub has_content_type_manager: Cell, pub content_type: Cell>, @@ -1213,7 +1213,7 @@ impl WlSurface { let had_frame_requests = self.buffer_had_frame_request.get(); let has_frame_requests = { let frs = &mut *self.frame_requests.borrow_mut(); - frs.extend(pending.frame_request.drain(..)); + frs.append(&mut pending.frame_request); frs.is_not_empty() }; self.buffer_had_frame_request @@ -1235,8 +1235,11 @@ impl WlSurface { self.opaque_region.set(region); } } + let mut tearing_changed = false; if let Some(tearing) = pending.tearing.take() { - self.tearing.set(tearing); + if self.tearing.replace(tearing) != tearing { + tearing_changed = true; + } } if let Some(content_type) = pending.content_type.take() { self.content_type.set(content_type); @@ -1305,6 +1308,13 @@ impl WlSurface { pending.buffer_damage.clear(); pending.surface_damage.clear(); pending.damage_full = false; + if tearing_changed { + if let Some(tl) = self.toplevel.get() { + if tl.tl_data().is_fullscreen.get() { + self.output.get().update_presentation_type(); + } + } + } Ok(()) } diff --git a/src/state.rs b/src/state.rs index 430b5d34..7af22022 100644 --- a/src/state.rs +++ b/src/state.rs @@ -64,8 +64,8 @@ use { time::Time, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, Node, NodeIds, - NodeVisitorBase, OutputNode, PlaceholderNode, ToplevelNode, ToplevelNodeBase, VrrMode, - WorkspaceNode, + NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelNode, + ToplevelNodeBase, VrrMode, WorkspaceNode, }, utils::{ activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings, @@ -203,6 +203,7 @@ pub struct State { pub damage_visualizer: DamageVisualizer, pub default_vrr_mode: Cell<&'static VrrMode>, pub default_vrr_cursor_hz: Cell>, + pub default_tearing_mode: Cell<&'static TearingMode>, } // impl Drop for State { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 0d4b09d6..ebdaca2f 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -125,6 +125,7 @@ impl ConnectorHandler { pos: Cell::new((x1, 0)), vrr_mode: Cell::new(self.state.default_vrr_mode.get()), vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()), + tearing_mode: Cell::new(self.state.default_tearing_mode.get()), }); self.state .persistent_output_states @@ -242,7 +243,7 @@ impl ConnectorHandler { } self.state.add_global(&global); self.state.tree_changed(); - on.update_vrr_state(); + on.update_presentation_type(); 'outer: loop { while let Some(event) = self.data.connector.event() { match event { diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index b256849c..a9ed6216 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -330,7 +330,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(2), + version: s.jay_compositor.1.min(3), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/src/tree/output.rs b/src/tree/output.rs index c18f0a83..e6100f84 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -43,7 +43,7 @@ use { wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id}, }, ahash::AHashMap, - jay_config::video::{Transform, VrrMode as ConfigVrrMode}, + jay_config::video::{TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode}, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, @@ -789,7 +789,12 @@ impl OutputNode { self.state.tree_changed(); } - pub fn update_vrr_state(&self) { + pub fn update_presentation_type(&self) { + self.update_vrr_state(); + self.update_tearing(); + } + + fn update_vrr_state(&self) { let enabled = match self.global.persistent.vrr_mode.get() { VrrMode::Never => false, VrrMode::Always => true, @@ -821,6 +826,33 @@ impl OutputNode { }; self.global.connector.connector.set_vrr_enabled(enabled); } + + fn update_tearing(&self) { + let enabled = match self.global.persistent.tearing_mode.get() { + TearingMode::Never => false, + TearingMode::Always => true, + TearingMode::Fullscreen { surface } => 'get: { + let Some(ws) = self.workspace.get() else { + break 'get false; + }; + let Some(tl) = ws.fullscreen.get() else { + break 'get false; + }; + if let Some(req) = surface { + let Some(surface) = tl.tl_scanout_surface() else { + break 'get false; + }; + if req.tearing_requested { + if !surface.tearing.get() { + break 'get false; + } + } + } + true + } + }; + self.global.connector.connector.set_tearing_enabled(enabled); + } } pub struct OutputTitle { @@ -1185,3 +1217,55 @@ impl VrrMode { } } } + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TearingMode { + Never, + Always, + Fullscreen { + surface: Option, + }, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct TearingSurfaceRequirements { + tearing_requested: bool, +} + +impl TearingMode { + pub const NEVER: &'static Self = &Self::Never; + pub const ALWAYS: &'static Self = &Self::Always; + pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; + pub const VARIANT_2: &'static Self = &Self::Fullscreen { + surface: Some(TearingSurfaceRequirements { + tearing_requested: false, + }), + }; + pub const VARIANT_3: &'static Self = &Self::Fullscreen { + surface: Some(TearingSurfaceRequirements { + tearing_requested: true, + }), + }; + + pub fn from_config(mode: ConfigTearingMode) -> Option<&'static Self> { + let res = match mode { + ConfigTearingMode::NEVER => Self::NEVER, + ConfigTearingMode::ALWAYS => Self::ALWAYS, + ConfigTearingMode::VARIANT_1 => Self::VARIANT_1, + ConfigTearingMode::VARIANT_2 => Self::VARIANT_2, + ConfigTearingMode::VARIANT_3 => Self::VARIANT_3, + _ => return None, + }; + Some(res) + } + + pub fn to_config(&self) -> ConfigVrrMode { + match self { + Self::NEVER => ConfigVrrMode::NEVER, + Self::ALWAYS => ConfigVrrMode::ALWAYS, + Self::VARIANT_1 => ConfigVrrMode::VARIANT_1, + Self::VARIANT_2 => ConfigVrrMode::VARIANT_2, + Self::VARIANT_3 => ConfigVrrMode::VARIANT_3, + } + } +} diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index c8feaff1..7daf641f 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -181,7 +181,7 @@ impl WorkspaceNode { surface.send_feedback(&fb); } } - self.output.get().update_vrr_state(); + self.output.get().update_presentation_type(); } pub fn remove_fullscreen_node(&self) { @@ -195,7 +195,7 @@ impl WorkspaceNode { surface.send_feedback(&fb); } } - self.output.get().update_vrr_state(); + self.output.get().update_presentation_type(); } } diff --git a/src/video/drm.rs b/src/video/drm.rs index 51d101d3..32c0a075 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -41,14 +41,15 @@ use crate::{ dmabuf::DmaBuf, drm::sys::{ auth_magic, drm_format_modifier, drm_format_modifier_blob, drop_master, get_version, - revoke_lease, DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + revoke_lease, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, DRM_CAP_CURSOR_HEIGHT, + DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, }, Modifier, INVALID_MODIFIER, }, }; pub use sys::{ drm_mode_modeinfo, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, - DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, + DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, DRM_MODE_PAGE_FLIP_EVENT, }; #[derive(Debug, Error)] @@ -339,6 +340,10 @@ impl DrmMaster { Ok((width, height)) } + pub fn supports_async_commit(&self) -> bool { + self.get_cap(DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP) == Ok(1) + } + pub fn get_connector_info( &self, connector: DrmConnector, diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index b1a431d8..0688af40 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -238,6 +238,7 @@ const DRM_MODE_PROP_ATOMIC: u32 = 0x80000000; pub const DRM_CAP_CURSOR_WIDTH: u64 = 0x8; pub const DRM_CAP_CURSOR_HEIGHT: u64 = 0x9; +pub const DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP: u64 = 0x15; #[repr(C)] struct drm_mode_property_enum { @@ -865,6 +866,7 @@ struct drm_mode_atomic { const DRM_IOCTL_MODE_ATOMIC: u64 = drm_iowr::(0xbc); pub const DRM_MODE_PAGE_FLIP_EVENT: u32 = 0x01; +pub const DRM_MODE_PAGE_FLIP_ASYNC: u32 = 0x02; pub const DRM_MODE_ATOMIC_TEST_ONLY: u32 = 0x0100; pub const DRM_MODE_ATOMIC_NONBLOCK: u32 = 0x0200; pub const DRM_MODE_ATOMIC_ALLOW_MODESET: u32 = 0x0400; diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index fa2cf949..625787b2 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -22,7 +22,7 @@ use { logging::LogLevel, status::MessageFormat, theme::Color, - video::{GfxApi, Transform, VrrMode}, + video::{GfxApi, TearingMode, Transform, VrrMode}, Axis, Direction, Workspace, }, std::{ @@ -207,6 +207,7 @@ pub struct Output { pub transform: Option, pub mode: Option, pub vrr: Option, + pub tearing: Option, } #[derive(Debug, Clone)] @@ -292,6 +293,11 @@ pub struct Vrr { pub cursor_hz: Option, } +#[derive(Debug, Clone)] +pub struct Tearing { + pub mode: Option, +} + #[derive(Debug, Clone)] pub struct Shortcut { pub mask: Modifiers, @@ -326,6 +332,7 @@ pub struct Config { pub focus_follows_mouse: bool, pub window_management_key: Option, pub vrr: Option, + pub tearing: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index 61f91c9b..b431f296 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -28,6 +28,7 @@ mod output_match; mod repeat_rate; pub mod shortcuts; mod status; +mod tearing; mod theme; mod vrr; diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 51de6c98..9b2bfe86 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -22,6 +22,7 @@ use { ShortcutsParserError, }, status::StatusParser, + tearing::TearingParser, theme::ThemeParser, vrr::VrrParser, }, @@ -108,6 +109,7 @@ impl Parser for ConfigParser<'_> { focus_follows_mouse, window_management_key_val, vrr_val, + tearing_val, ), ) = ext.extract(( ( @@ -141,6 +143,7 @@ impl Parser for ConfigParser<'_> { recover(opt(bol("focus-follows-mouse"))), recover(opt(str("window-management-key"))), opt(val("vrr")), + opt(val("tearing")), ), ))?; let mut keymap = None; @@ -314,6 +317,15 @@ impl Parser for ConfigParser<'_> { } } } + let mut tearing = None; + if let Some(value) = tearing_val { + match value.parse(&mut TearingParser(self.0)) { + Ok(v) => tearing = Some(v), + Err(e) => { + log::warn!("Could not parse tearing setting: {}", self.0.error(e)); + } + } + } Ok(Config { keymap, repeat_rate, @@ -339,6 +351,7 @@ impl Parser for ConfigParser<'_> { focus_follows_mouse: focus_follows_mouse.despan().unwrap_or(true), window_management_key, vrr, + tearing, }) } } diff --git a/toml-config/src/config/parsers/output.rs b/toml-config/src/config/parsers/output.rs index 6bde6b32..4348cd47 100644 --- a/toml-config/src/config/parsers/output.rs +++ b/toml-config/src/config/parsers/output.rs @@ -7,6 +7,7 @@ use { parsers::{ mode::ModeParser, output_match::{OutputMatchParser, OutputMatchParserError}, + tearing::TearingParser, vrr::VrrParser, }, Output, @@ -47,16 +48,18 @@ impl<'a> Parser for OutputParser<'a> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.cx, span, table); - let (name, match_val, x, y, scale, transform, mode, vrr_val) = ext.extract(( - opt(str("name")), - val("match"), - recover(opt(s32("x"))), - recover(opt(s32("y"))), - recover(opt(fltorint("scale"))), - recover(opt(str("transform"))), - opt(val("mode")), - opt(val("vrr")), - ))?; + let (name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val) = + ext.extract(( + opt(str("name")), + val("match"), + recover(opt(s32("x"))), + recover(opt(s32("y"))), + recover(opt(fltorint("scale"))), + recover(opt(str("transform"))), + opt(val("mode")), + opt(val("vrr")), + opt(val("tearing")), + ))?; let transform = match transform { None => None, Some(t) => match t.value { @@ -107,6 +110,15 @@ impl<'a> Parser for OutputParser<'a> { } } } + let mut tearing = None; + if let Some(value) = tearing_val { + match value.parse(&mut TearingParser(self.cx)) { + Ok(v) => tearing = Some(v), + Err(e) => { + log::warn!("Could not parse tearing setting: {}", self.cx.error(e)); + } + } + } Ok(Output { name: name.despan().map(|v| v.to_string()), match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?, @@ -116,6 +128,7 @@ impl<'a> Parser for OutputParser<'a> { transform, mode, vrr, + tearing, }) } } diff --git a/toml-config/src/config/parsers/tearing.rs b/toml-config/src/config/parsers/tearing.rs new file mode 100644 index 00000000..ec991681 --- /dev/null +++ b/toml-config/src/config/parsers/tearing.rs @@ -0,0 +1,78 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{opt, val, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + Tearing, + }, + toml::{ + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + jay_config::video::TearingMode, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum TearingParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct TearingParser<'a>(pub &'a Context<'a>); + +impl Parser for TearingParser<'_> { + type Value = Tearing; + type Error = TearingParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let mode = ext.extract(opt(val("mode")))?; + let mode = mode.and_then(|m| match m.parse(&mut TearingModeParser) { + Ok(m) => Some(m), + Err(e) => { + log::error!("Could not parse mode: {}", self.0.error(e)); + None + } + }); + Ok(Tearing { mode }) + } +} + +#[derive(Debug, Error)] +pub enum TearingModeParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error("Unknown mode {0}")] + UnknownMode(String), +} + +struct TearingModeParser; + +impl Parser for TearingModeParser { + type Value = TearingMode; + type Error = TearingModeParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + let mode = match string { + "never" => TearingMode::NEVER, + "always" => TearingMode::ALWAYS, + "variant1" => TearingMode::VARIANT_1, + "variant2" => TearingMode::VARIANT_2, + "variant3" => TearingMode::VARIANT_3, + _ => return Err(TearingModeParserError::UnknownMode(string.to_string()).spanned(span)), + }; + Ok(mode) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 4fd3e58a..4651bf7e 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -30,8 +30,8 @@ use { video::{ connectors, drm_devices, on_connector_connected, on_connector_disconnected, on_graphics_initialized, on_new_connector, on_new_drm_device, - set_direct_scanout_enabled, set_gfx_api, set_vrr_cursor_hz, set_vrr_mode, Connector, - DrmDevice, + set_direct_scanout_enabled, set_gfx_api, set_tearing_mode, set_vrr_cursor_hz, + set_vrr_mode, Connector, DrmDevice, }, }, std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc}, @@ -564,6 +564,11 @@ impl Output { c.set_vrr_cursor_hz(hz); } } + if let Some(tearing) = &self.tearing { + if let Some(mode) = tearing.mode { + c.set_tearing_mode(mode); + } + } } } @@ -1034,6 +1039,11 @@ fn load_config(initial_load: bool, persistent: &Rc) { set_vrr_cursor_hz(hz); } } + if let Some(tearing) = config.tearing { + if let Some(mode) = tearing.mode { + set_tearing_mode(mode); + } + } } fn create_command(exec: &Exec) -> Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 07b3fe2d..4b335ee2 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -581,6 +581,10 @@ "vrr": { "description": "Configures the default VRR settings.\n\nThis can be overwritten for individual outputs.\n\nBy default, the VRR mode is `never` and the cursor refresh rate is unbounded.\n\n- Example:\n \n ```toml\n vrr = { mode = \"always\", cursor-hz = 90 }\n ```\n", "$ref": "#/$defs/Vrr" + }, + "tearing": { + "description": "Configures the default tearing settings.\n\nThis can be overwritten for individual outputs.\n\nBy default, the tearing mode is `variant3`.\n\n- Example:\n\n ```toml\n tearing.mode = \"never\"\n ```\n", + "$ref": "#/$defs/Tearing" } }, "required": [] @@ -1031,6 +1035,10 @@ "vrr": { "description": "Configures the VRR settings of this output.\n\nBy default, the VRR mode is `never` and the cursor refresh rate is unbounded.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n vrr = { mode = \"always\", cursor-hz = 90 }\n ```\n", "$ref": "#/$defs/Vrr" + }, + "tearing": { + "description": "Configures the tearing settings of this output.\n\nBy default, the tearing mode is `variant3`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n tearing.mode = \"never\"\n ```\n", + "$ref": "#/$defs/Tearing" } }, "required": [ @@ -1148,6 +1156,28 @@ "exec" ] }, + "Tearing": { + "description": "Describes tearing settings.\n\n- Example:\n\n ```toml\n tearing.mode = \"never\"\n ```\n", + "type": "object", + "properties": { + "mode": { + "description": "The tearing mode.", + "$ref": "#/$defs/TearingMode" + } + }, + "required": [] + }, + "TearingMode": { + "type": "string", + "description": "The tearing mode of an output.\n\n- Example:\n\n ```toml\n tearing.mode = \"never\"\n ```\n", + "enum": [ + "always", + "never", + "variant1", + "variant2", + "variant3" + ] + }, "Theme": { "description": "The theme of the compositor.\n", "type": "object", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index ae47890c..7e615924 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1126,6 +1126,22 @@ The table has the following fields: The value of this field should be a [Vrr](#types-Vrr). +- `tearing` (optional): + + Configures the default tearing settings. + + This can be overwritten for individual outputs. + + By default, the tearing mode is `variant3`. + + - Example: + + ```toml + tearing.mode = "never" + ``` + + The value of this field should be a [Tearing](#types-Tearing). + ### `Connector` @@ -2198,6 +2214,22 @@ The table has the following fields: The value of this field should be a [Vrr](#types-Vrr). +- `tearing` (optional): + + Configures the tearing settings of this output. + + By default, the tearing mode is `variant3`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + tearing.mode = "never" + ``` + + The value of this field should be a [Tearing](#types-Tearing). + ### `OutputMatch` @@ -2532,6 +2564,66 @@ The table has the following fields: The value of this field should be a string. + +### `Tearing` + +Describes tearing settings. + +- Example: + + ```toml + tearing.mode = "never" + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `mode` (optional): + + The tearing mode. + + The value of this field should be a [TearingMode](#types-TearingMode). + + + +### `TearingMode` + +The tearing mode of an output. + +- Example: + + ```toml + tearing.mode = "never" + ``` + +Values of this type should be strings. + +The string should have one of the following values: + +- `always`: + + Tearing is never enabled. + +- `never`: + + Tearing is always enabled. + +- `variant1`: + + Tearing is enabled when one or more applications are displayed fullscreen. + +- `variant2`: + + Tearing is enabled when a single application is displayed fullscreen. + +- `variant3`: + + Tearing is enabled when a single application is displayed and the application has + requested tearing. + + + ### `Theme` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index af9e1ac4..7b16e0e8 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1573,6 +1573,21 @@ Output: match.serial-number = "33K03894SL0" vrr = { mode = "always", cursor-hz = 90 } ``` + tearing: + ref: Tearing + required: false + description: | + Configures the tearing settings of this output. + + By default, the tearing mode is `variant3`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + tearing.mode = "never" + ``` Transform: @@ -2180,6 +2195,21 @@ Config: ```toml vrr = { mode = "always", cursor-hz = 90 } ``` + tearing: + ref: Tearing + required: false + description: | + Configures the default tearing settings. + + This can be overwritten for individual outputs. + + By default, the tearing mode is `variant3`. + + - Example: + + ```toml + tearing.mode = "never" + ``` Idle: @@ -2367,3 +2397,45 @@ VrrHz: description: The string `none` can be used to disable the limiter. - kind: number description: The refresh rate in HZ. + + +Tearing: + kind: table + description: | + Describes tearing settings. + + - Example: + + ```toml + tearing.mode = "never" + ``` + fields: + mode: + ref: TearingMode + required: false + description: The tearing mode. + + +TearingMode: + description: | + The tearing mode of an output. + + - Example: + + ```toml + tearing.mode = "never" + ``` + kind: string + values: + - value: always + description: Tearing is never enabled. + - value: never + description: Tearing is always enabled. + - value: variant1 + description: Tearing is enabled when one or more applications are displayed fullscreen. + - value: variant2 + description: Tearing is enabled when a single application is displayed fullscreen. + - value: variant3 + description: | + Tearing is enabled when a single application is displayed and the application has + requested tearing. diff --git a/wire/jay_randr.txt b/wire/jay_randr.txt index c4b8701c..cfd4b8a8 100644 --- a/wire/jay_randr.txt +++ b/wire/jay_randr.txt @@ -65,6 +65,11 @@ request set_vrr_cursor_hz (since = 2) { hz: pod(f64), } +request set_tearing_mode (since = 3) { + output: str, + mode: u32, +} + # events event global { @@ -132,3 +137,7 @@ event vrr_state (since = 2) { event vrr_cursor_hz (since = 2) { hz: pod(f64), } + +event tearing_state (since = 3) { + mode: u32, +}