Skip to content

Commit

Permalink
Implement wayland side of tearing control
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex committed Apr 19, 2024
1 parent c293ec7 commit 0cb7fa4
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub mod shell;
pub mod shm;
pub mod socket;
pub mod tablet_manager;
pub mod tearing_control;
pub mod text_input;
pub mod viewporter;
pub mod virtual_keyboard;
Expand Down
141 changes: 141 additions & 0 deletions src/wayland/tearing_control/dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use wayland_protocols::wp::tearing_control::v1::server::{
wp_tearing_control_manager_v1::{self, WpTearingControlManagerV1},
wp_tearing_control_v1::{self, WpTearingControlV1},
};
use wayland_server::{
backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};

use super::{
TearingControlState, TearingControlSurfaceCachedState, TearingControlSurfaceData, TearingControlUserData,
};
use crate::wayland::compositor;

impl<D> GlobalDispatch<WpTearingControlManagerV1, (), D> for TearingControlState
where
D: GlobalDispatch<WpTearingControlManagerV1, ()>,
D: Dispatch<WpTearingControlManagerV1, ()>,
D: Dispatch<WpTearingControlV1, TearingControlUserData>,
D: 'static,
{
fn bind(
_state: &mut D,
_: &DisplayHandle,
_: &Client,
resource: New<WpTearingControlManagerV1>,
_: &(),
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, ());
}
}

impl<D> Dispatch<WpTearingControlManagerV1, (), D> for TearingControlState
where
D: Dispatch<WpTearingControlManagerV1, ()>,
D: Dispatch<WpTearingControlV1, TearingControlUserData>,
D: 'static,
{
fn request(
_state: &mut D,
_: &Client,
manager: &WpTearingControlManagerV1,
request: wp_tearing_control_manager_v1::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
wp_tearing_control_manager_v1::Request::GetTearingControl { id, surface } => {
let already_taken = compositor::with_states(&surface, |states| {
states
.data_map
.insert_if_missing_threadsafe(TearingControlSurfaceData::new);
let data = states.data_map.get::<TearingControlSurfaceData>().unwrap();

let already_taken = data.is_resource_attached();

if !already_taken {
data.set_is_resource_attached(true);
}

already_taken
});

if already_taken {
manager.post_error(
wp_tearing_control_manager_v1::Error::TearingControlExists,
"WlSurface already has WpTearingControlV1 attached",
)
} else {
data_init.init(id, TearingControlUserData::new(surface));
}
}

wp_tearing_control_manager_v1::Request::Destroy => {}
_ => unreachable!(),
}
}
}

impl<D> Dispatch<WpTearingControlV1, TearingControlUserData, D> for TearingControlState
where
D: Dispatch<WpTearingControlV1, TearingControlUserData>,
{
fn request(
_state: &mut D,
_: &Client,
_: &WpTearingControlV1,
request: wp_tearing_control_v1::Request,
data: &TearingControlUserData,
_dh: &DisplayHandle,
_: &mut DataInit<'_, D>,
) {
match request {
wp_tearing_control_v1::Request::SetPresentationHint { hint } => {
let wayland_server::WEnum::Value(hint) = hint else {
return;
};
let surface = data.wl_surface();

compositor::with_states(&surface, |states| {
states
.cached_state
.pending::<TearingControlSurfaceCachedState>()
.presentation_hint = hint;
})
}
// Switch back to default PresentationHint.
// This is equivalent to setting the hint to Vsync,
// including double buffering semantics.
wp_tearing_control_v1::Request::Destroy => {
let surface = data.wl_surface();

compositor::with_states(&surface, |states| {
states
.data_map
.get::<TearingControlSurfaceData>()
.unwrap()
.set_is_resource_attached(false);

states
.cached_state
.pending::<TearingControlSurfaceCachedState>()
.presentation_hint = wp_tearing_control_v1::PresentationHint::Vsync;
});
}
_ => unreachable!(),
}
}

fn destroyed(
_state: &mut D,
_client: ClientId,
_object: &WpTearingControlV1,
_data: &TearingControlUserData,
) {
// Nothing to do here, graceful Destroy is already handled with double buffering
// and in case of client close WlSurface destroyed handler will clean up the data anyway,
// so there is no point in queuing new update
}
}
200 changes: 200 additions & 0 deletions src/wayland/tearing_control/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//! Implementation of wp_tearing_control protocol
//!
//! ### Example
//!
//! ```no_run
//! # extern crate wayland_server;
//! #
//! use wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle};
//! use smithay::{
//! delegate_tearing_control, delegate_compositor,
//! wayland::compositor::{self, CompositorState, CompositorClientState, CompositorHandler},
//! wayland::tearing_control::{TearingControlSurfaceCachedState, TearingControlState},
//! };
//!
//! pub struct State {
//! compositor_state: CompositorState,
//! };
//! struct ClientState { compositor_state: CompositorClientState }
//! impl wayland_server::backend::ClientData for ClientState {}
//!
//! delegate_tearing_control!(State);
//! delegate_compositor!(State);
//!
//! impl CompositorHandler for State {
//! fn compositor_state(&mut self) -> &mut CompositorState {
//! &mut self.compositor_state
//! }
//!
//! fn client_compositor_state<'a>(&self, client: &'a wayland_server::Client) -> &'a CompositorClientState {
//! &client.get_data::<ClientState>().unwrap().compositor_state
//! }
//!
//! fn commit(&mut self, surface: &WlSurface) {
//! compositor::with_states(&surface, |states| {
//! let current = states.cached_state.current::<TearingControlSurfaceCachedState>();
//! dbg!(current.presentation_hint());
//! });
//! }
//! }
//!
//! let mut display = wayland_server::Display::<State>::new().unwrap();
//!
//! let compositor_state = CompositorState::new::<State>(&display.handle());
//! TearingControlState::new::<State>(&display.handle());
//!
//! let state = State {
//! compositor_state,
//! };
//! ```
use std::sync::{
atomic::{self, AtomicBool},
Mutex,
};

use wayland_protocols::wp::tearing_control::v1::server::{
wp_tearing_control_manager_v1::WpTearingControlManagerV1,
wp_tearing_control_v1::{self, WpTearingControlV1},
};
use wayland_server::{
backend::GlobalId, protocol::wl_surface::WlSurface, Dispatch, DisplayHandle, GlobalDispatch,
};

use super::compositor::Cacheable;

mod dispatch;

/// Data associated with WlSurface
/// Represents the client pending state
///
/// ```no_run
/// use smithay::wayland::compositor;
/// use smithay::wayland::tearing_control::TearingControlSurfaceCachedState;
///
/// # let wl_surface = todo!();
/// compositor::with_states(&wl_surface, |states| {
/// let current = states.cached_state.current::<TearingControlSurfaceCachedState>();
/// dbg!(current.presentation_hint());
/// });
/// ```
#[derive(Debug, Clone, Copy)]
pub struct TearingControlSurfaceCachedState {
presentation_hint: wp_tearing_control_v1::PresentationHint,
}

impl TearingControlSurfaceCachedState {
/// Provides information for if submitted frames from the client may be presented with tearing.
pub fn presentation_hint(&self) -> &wp_tearing_control_v1::PresentationHint {
&self.presentation_hint
}
}

impl Default for TearingControlSurfaceCachedState {
fn default() -> Self {
Self {
presentation_hint: wp_tearing_control_v1::PresentationHint::Vsync,
}
}
}

impl Cacheable for TearingControlSurfaceCachedState {
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
*self
}

fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
*into = self;
}
}

#[derive(Debug)]
struct TearingControlSurfaceData {
is_resource_attached: AtomicBool,
}

impl TearingControlSurfaceData {
fn new() -> Self {
Self {
is_resource_attached: AtomicBool::new(false),
}
}

fn set_is_resource_attached(&self, is_attached: bool) {
self.is_resource_attached
.store(is_attached, atomic::Ordering::Release)
}

fn is_resource_attached(&self) -> bool {
self.is_resource_attached.load(atomic::Ordering::Acquire)
}
}

/// User data of [WpTearingControlV1] object
#[derive(Debug)]
pub struct TearingControlUserData(Mutex<WlSurface>);

impl TearingControlUserData {
fn new(surface: WlSurface) -> Self {
Self(Mutex::new(surface))
}

fn wl_surface(&self) -> WlSurface {
self.0.lock().unwrap().clone()
}
}

/// Delegate type for [WpTearingControlManagerV1] global.
#[derive(Debug)]
pub struct TearingControlState {
global: GlobalId,
}

impl TearingControlState {
/// Regiseter new [WpTearingControlManagerV1] global
pub fn new<D>(display: &DisplayHandle) -> TearingControlState
where
D: GlobalDispatch<WpTearingControlManagerV1, ()>
+ Dispatch<WpTearingControlManagerV1, ()>
+ Dispatch<WpTearingControlV1, TearingControlUserData>
+ 'static,
{
let global = display.create_global::<D, WpTearingControlManagerV1, _>(1, ());

TearingControlState { global }
}

/// Returns the [WpTearingControlManagerV1] global id
pub fn global(&self) -> GlobalId {
self.global.clone()
}
}

/// Macro to delegate implementation of the wp tearing control protocol
#[macro_export]
macro_rules! delegate_tearing_control {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
type __WpTearingControlManagerV1 =
$crate::reexports::wayland_protocols::wp::tearing_control::v1::server::wp_tearing_control_manager_v1::WpTearingControlManagerV1;
type __WpTearingControlV1 =
$crate::reexports::wayland_protocols::wp::tearing_control::v1::server::wp_tearing_control_v1::WpTearingControlV1;

$crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__WpTearingControlManagerV1: ()
] => $crate::wayland::tearing_control::TearingControlState
);

$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__WpTearingControlManagerV1: ()
] => $crate::wayland::tearing_control::TearingControlState
);

$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__WpTearingControlV1: $crate::wayland::tearing_control::TearingControlUserData
] => $crate::wayland::tearing_control::TearingControlState
);
};
}

0 comments on commit 0cb7fa4

Please sign in to comment.