1
0
Fork 0
forked from wry/wry

Merge pull request #228 from mahkoh/jorth/tearing

Implement tearing
This commit is contained in:
mahkoh 2024-07-19 23:11:14 +02:00 committed by GitHub
commit 9dc58129b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 727 additions and 52 deletions

View file

@ -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.

View file

@ -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<Connector>, mode: TearingMode) {
self.send(&ClientMessage::SetTearingMode { connector, mode })
}
pub fn drm_devices(&self) -> Vec<DrmDevice> {
let res = self.send_with_response(&ClientMessage::GetDrmDevices);
get_response!(res, vec![], GetDrmDevices { devices });

View file

@ -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<Connector>,
hz: f64,
},
SetTearingMode {
connector: Option<Connector>,
mode: TearingMode,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -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)
}

View file

@ -2,6 +2,7 @@
- Add fine-grained damage tracking.
- Add support for adaptive sync.
- Add support for tearing.
# 1.4.0 (2024-07-07)

View file

@ -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)]

View file

@ -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<Rc<MetalRenderContext>>,
@ -456,6 +458,8 @@ pub struct MetalConnector {
pub active_framebuffer: RefCell<Option<PresentFb>>,
pub next_framebuffer: OpaqueCell<Option<PresentFb>>,
pub direct_scanout_active: Cell<bool>,
pub tearing_requested: Cell<bool>,
}
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<T: Copy> {
pub id: DrmProperty,
pub value: Cell<T>,
pub pending_value: Cell<Option<T>>,
}
impl<T: Copy> MutableProperty<T> {
@ -1830,6 +1885,7 @@ impl<T: Copy> MutableProperty<T> {
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),

View file

@ -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<f64>,
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();

View file

@ -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<State>) {
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(),

View file

@ -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<Connector>,
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 {

View file

@ -66,7 +66,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
2
3
}
fn required_caps(&self) -> ClientCaps {

View file

@ -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<Client>, 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<Self>,
) -> 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<ClientError>),
#[error("Unknown VRR mode {0}")]
UnknownVrrMode(u32),
#[error("Unknown tearing mode {0}")]
UnknownTearingMode(u32),
}
efrom!(JayRandrError, ClientError);

View file

@ -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<Option<f64>>,
pub tearing_mode: Cell<&'static TearingMode>,
}
#[derive(Eq, PartialEq, Hash)]

View file

@ -287,7 +287,7 @@ pub struct WlSurface {
pub constraints: SmallMap<SeatId, Rc<SeatConstraint>, 1>,
xwayland_serial: Cell<Option<u64>>,
tearing_control: CloneCell<Option<Rc<WpTearingControlV1>>>,
tearing: Cell<bool>,
pub tearing: Cell<bool>,
version: Version,
pub has_content_type_manager: Cell<bool>,
pub content_type: Cell<Option<ContentType>>,
@ -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(())
}

View file

@ -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<Option<f64>>,
pub default_tearing_mode: Cell<&'static TearingMode>,
}
// impl Drop for State {

View file

@ -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 {

View file

@ -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));

View file

@ -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<TearingSurfaceRequirements>,
},
}
#[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,
}
}
}

View file

@ -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();
}
}

View file

@ -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,

View file

@ -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::<drm_mode_atomic>(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;

View file

@ -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<Transform>,
pub mode: Option<Mode>,
pub vrr: Option<Vrr>,
pub tearing: Option<Tearing>,
}
#[derive(Debug, Clone)]
@ -292,6 +293,11 @@ pub struct Vrr {
pub cursor_hz: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct Tearing {
pub mode: Option<TearingMode>,
}
#[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<ModifiedKeySym>,
pub vrr: Option<Vrr>,
pub tearing: Option<Tearing>,
}
#[derive(Debug, Error)]

View file

@ -28,6 +28,7 @@ mod output_match;
mod repeat_rate;
pub mod shortcuts;
mod status;
mod tearing;
mod theme;
mod vrr;

View file

@ -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,
})
}
}

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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,
})
}
}

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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<Self> {
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)
}
}

View file

@ -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<PersistentState>) {
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 {

View file

@ -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",

View file

@ -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).
<a name="types-Connector"></a>
### `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).
<a name="types-OutputMatch"></a>
### `OutputMatch`
@ -2532,6 +2564,66 @@ The table has the following fields:
The value of this field should be a string.
<a name="types-Tearing"></a>
### `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).
<a name="types-TearingMode"></a>
### `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.
<a name="types-Theme"></a>
### `Theme`

View file

@ -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.

View file

@ -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,
}