From 595eb4d9f5f4636e5d8783463b76bfb44fb84d5c Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 20 Jan 2024 04:37:23 +0100 Subject: [PATCH] Implement single-pixel buffer protocol --- src/catacomb.rs | 10 +- src/drawing.rs | 89 +++++++++++------- src/protocols/mod.rs | 1 + src/protocols/single_pixel_buffer/handlers.rs | 82 +++++++++++++++++ src/protocols/single_pixel_buffer/mod.rs | 92 +++++++++++++++++++ src/windows/window.rs | 53 ++++++----- 6 files changed, 270 insertions(+), 57 deletions(-) create mode 100644 src/protocols/single_pixel_buffer/handlers.rs create mode 100644 src/protocols/single_pixel_buffer/mod.rs diff --git a/src/catacomb.rs b/src/catacomb.rs index 21ad45e..e5af8e6 100644 --- a/src/catacomb.rs +++ b/src/catacomb.rs @@ -90,10 +90,14 @@ use crate::output::Output; use crate::protocols::idle_notify::{IdleNotifierHandler, IdleNotifierState}; use crate::protocols::screencopy::frame::Screencopy; use crate::protocols::screencopy::{ScreencopyHandler, ScreencopyManagerState}; +use crate::protocols::single_pixel_buffer::SinglePixelBufferState; use crate::udev::Udev; use crate::windows::surface::Surface; use crate::windows::Windows; -use crate::{dbus, delegate_idle_notify, delegate_screencopy, ipc_server, trace_error}; +use crate::{ + dbus, delegate_idle_notify, delegate_screencopy, delegate_single_pixel_buffer, ipc_server, + trace_error, +}; /// Time before xdg_activation tokens are invalidated. const ACTIVATION_TIMEOUT: Duration = Duration::from_secs(10); @@ -220,6 +224,8 @@ impl Catacomb { let clock_id = libc::CLOCK_MONOTONIC as u32; PresentationState::new::(&display_handle, clock_id); + SinglePixelBufferState::new::(&display_handle); + // Initialize idle-inhibit protocol. IdleInhibitManagerState::new::(&display_handle); @@ -850,3 +856,5 @@ impl ClientData for ClientState { fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} } + +delegate_single_pixel_buffer!(Catacomb); diff --git a/src/drawing.rs b/src/drawing.rs index ee8f78f..4e836e4 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -11,6 +11,7 @@ use smithay::backend::renderer::element::{Element, Id, RenderElement, Underlying use smithay::backend::renderer::gles::{ffi, GlesFrame, GlesRenderer, GlesTexture}; use smithay::backend::renderer::utils::{Buffer, CommitCounter, DamageBag, DamageSnapshot}; use smithay::backend::renderer::{self, Renderer, Texture as _}; +use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::utils::{ Buffer as BufferSpace, Logical, Physical, Point, Rectangle, Scale, Size, Transform, @@ -22,6 +23,7 @@ use smithay::wayland::viewporter::{self, ViewportCachedState}; use crate::geometry::SubtractRectFast; use crate::output::{Canvas, GESTURE_HANDLE_HEIGHT}; +use crate::protocols::single_pixel_buffer; /// Color of the hovered overview tiling location highlight. const ACTIVE_DROP_TARGET_RGBA: [u8; 4] = [64, 64, 64, 128]; @@ -129,37 +131,7 @@ impl Texture { height: i32, opaque: bool, ) -> Self { - assert!(buffer.len() as i32 >= width * height * 4); - - let format = ffi::RGBA; - let texture_id = renderer - .with_context(|gl| unsafe { - let mut tex = 0; - gl.GenTextures(1, &mut tex); - gl.BindTexture(ffi::TEXTURE_2D, tex); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); - gl.TexImage2D( - ffi::TEXTURE_2D, - 0, - format as i32, - width, - height, - 0, - format, - ffi::UNSIGNED_BYTE, - buffer.as_ptr().cast(), - ); - gl.BindTexture(ffi::TEXTURE_2D, 0); - - tex - }) - .expect("create texture"); - - let size = (width, height).into(); - let texture = - unsafe { GlesTexture::from_raw(renderer, Some(format), opaque, texture_id, size) }; - + let texture = create_texture(renderer, buffer, width, height, opaque); let logical_size = Size::::from((width, height)).to_f64().to_logical(scale).to_i32_round(); Texture::new(texture, logical_size, scale, opaque) @@ -453,9 +425,7 @@ impl CatacombSurfaceData { let old_size = self.buffer_size; self.scale = attributes.buffer_scale; self.transform = attributes.buffer_transform.into(); - self.buffer_size = renderer::buffer_dimensions(&buffer) - .unwrap_or_default() - .to_logical(self.scale, self.transform); + self.buffer_size = buffer_dimensions(&buffer, self.scale, self.transform); self.buffer = Some(Buffer::from(buffer)); self.texture = None; @@ -507,6 +477,18 @@ impl CatacombSurfaceData { } } +/// Get WlBuffer dimensions. +/// +/// NOTE: This can be removed once the single-pixel buffer protocol is supported +/// upstream. +fn buffer_dimensions(buffer: &WlBuffer, scale: i32, transform: Transform) -> Size { + match single_pixel_buffer::get_single_pixel_buffer(buffer) { + Ok(_) => Size::from((1, 1)), + Err(_) => renderer::buffer_dimensions(buffer).unwrap_or_default(), + } + .to_logical(scale, transform) +} + /// Pending buffer damage. #[derive(Default)] pub struct Damage { @@ -525,3 +507,42 @@ impl Damage { self.buffer.clear(); } } + +/// Create a new OpenGL texture. +pub fn create_texture( + renderer: &mut GlesRenderer, + buffer: &[u8], + width: i32, + height: i32, + opaque: bool, +) -> GlesTexture { + assert!(buffer.len() as i32 >= width * height * 4); + + let format = ffi::RGBA; + let texture_id = renderer + .with_context(|gl| unsafe { + let mut tex = 0; + gl.GenTextures(1, &mut tex); + gl.BindTexture(ffi::TEXTURE_2D, tex); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); + gl.TexImage2D( + ffi::TEXTURE_2D, + 0, + format as i32, + width, + height, + 0, + format, + ffi::UNSIGNED_BYTE, + buffer.as_ptr().cast(), + ); + gl.BindTexture(ffi::TEXTURE_2D, 0); + + tex + }) + .expect("create texture"); + + let size = (width, height).into(); + unsafe { GlesTexture::from_raw(renderer, Some(format), opaque, texture_id, size) } +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 24debb5..3282333 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1,2 +1,3 @@ pub mod idle_notify; pub mod screencopy; +pub mod single_pixel_buffer; diff --git a/src/protocols/single_pixel_buffer/handlers.rs b/src/protocols/single_pixel_buffer/handlers.rs new file mode 100644 index 0000000..dc1bca2 --- /dev/null +++ b/src/protocols/single_pixel_buffer/handlers.rs @@ -0,0 +1,82 @@ +use smithay::reexports::wayland_server::protocol::wl_buffer::{self, WlBuffer}; +use smithay::reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, +}; +use smithay::wayland::buffer::BufferHandler; +use smithay::reexports::wayland_protocols::wp::single_pixel_buffer::v1::server::wp_single_pixel_buffer_manager_v1::{ + self, WpSinglePixelBufferManagerV1, +}; + +use crate::protocols::single_pixel_buffer::{SinglePixelBufferState, SinglePixelBufferUserData}; + +impl GlobalDispatch for SinglePixelBufferState +where + D: GlobalDispatch, + D: Dispatch, + D: 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &(), + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } +} + +impl Dispatch for SinglePixelBufferState +where + D: Dispatch, + D: Dispatch, + D: 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _manager: &WpSinglePixelBufferManagerV1, + request: wp_single_pixel_buffer_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + wp_single_pixel_buffer_manager_v1::Request::CreateU32RgbaBuffer { + id: buffer, + r, + g, + b, + a, + } => { + data_init.init(buffer, SinglePixelBufferUserData { r, g, b, a }); + }, + wp_single_pixel_buffer_manager_v1::Request::Destroy => {}, + _ => unimplemented!(), + } + } +} + +impl Dispatch for SinglePixelBufferState +where + D: Dispatch, + D: BufferHandler, +{ + fn request( + data: &mut D, + _client: &Client, + buffer: &wl_buffer::WlBuffer, + request: wl_buffer::Request, + _udata: &SinglePixelBufferUserData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + wl_buffer::Request::Destroy => { + data.buffer_destroyed(buffer); + }, + _ => unreachable!(), + } + } +} diff --git a/src/protocols/single_pixel_buffer/mod.rs b/src/protocols/single_pixel_buffer/mod.rs new file mode 100644 index 0000000..aeb411f --- /dev/null +++ b/src/protocols/single_pixel_buffer/mod.rs @@ -0,0 +1,92 @@ +//! Utilities for handling the `wp_single_pixel_buffer` protocol + +use _single_pixel_buffer::v1::server::wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1; +use smithay::reexports::wayland_protocols::wp::single_pixel_buffer as _single_pixel_buffer; +use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; +use smithay::reexports::wayland_server::{Dispatch, DisplayHandle, GlobalDispatch, Resource}; + +mod handlers; + +/// Delegate state of WpSinglePixelBuffer protocol +#[derive(Debug)] +pub struct SinglePixelBufferState; + +impl SinglePixelBufferState { + /// Create a new [`WpSinglePixelBufferManagerV1`] global + // + /// The id provided by [`SinglePixelBufferState::global`] may be used to + /// remove or disable this global in the future. + pub fn new(display: &DisplayHandle) -> Self + where + D: GlobalDispatch, + D: Dispatch, + D: 'static, + { + display.create_global::(1, ()); + + Self + } +} + +/// User data of `WlBuffer` backed by single pixel +#[derive(Debug)] +pub struct SinglePixelBufferUserData { + /// Value of the buffer's red channel + pub r: u32, + /// Value of the buffer's green channel + pub g: u32, + /// Value of the buffer's blue channel + pub b: u32, + /// Value of the buffer's alpha channel + pub a: u32, +} + +impl SinglePixelBufferUserData { + /// Check if pixel has alpha + pub fn has_alpha(&self) -> bool { + self.a != u32::MAX + } + + /// RGAB8888 color buffer + pub fn rgba8888(&self) -> [u8; 4] { + let divisor = u32::MAX / 255; + + [ + (self.r / divisor) as u8, + (self.g / divisor) as u8, + (self.b / divisor) as u8, + (self.a / divisor) as u8, + ] + } +} + +/// Error that can occur when accessing an SinglePixelBuffer +#[derive(Debug)] +pub enum BufferAccessError { + /// This buffer is not managed by the SinglePixelBuffer handler + NotManaged, +} + +/// Gets the data of a `SinglePixelBuffer` backed [`WlBuffer`]. +pub fn get_single_pixel_buffer( + buffer: &WlBuffer, +) -> Result<&SinglePixelBufferUserData, BufferAccessError> { + buffer.data::().ok_or(BufferAccessError::NotManaged) +} + +/// Macro used to delegate `WpSinglePixelBuffer` events +#[macro_export] +macro_rules! delegate_single_pixel_buffer { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::wp::single_pixel_buffer::v1::server::wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1: () + ] => $crate::protocols::single_pixel_buffer::SinglePixelBufferState); + + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::wp::single_pixel_buffer::v1::server::wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1: () + ] => $crate::protocols::single_pixel_buffer::SinglePixelBufferState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer: $crate::protocols::single_pixel_buffer::SinglePixelBufferUserData + ] => $crate::protocols::single_pixel_buffer::SinglePixelBufferState); + }; +} diff --git a/src/windows/window.rs b/src/windows/window.rs index eaa0461..b661ede 100644 --- a/src/windows/window.rs +++ b/src/windows/window.rs @@ -35,9 +35,10 @@ use smithay::wayland::shell::xdg::{ }; use tracing::error; -use crate::drawing::{CatacombElement, CatacombSurfaceData, RenderTexture, Texture}; +use crate::drawing::{self, CatacombElement, CatacombSurfaceData, RenderTexture, Texture}; use crate::geometry::Vector; use crate::output::{ExclusiveSpace, Output}; +use crate::protocols::single_pixel_buffer; use crate::windows; use crate::windows::surface::{CatacombLayerSurface, InputSurface, Surface}; @@ -291,35 +292,43 @@ impl Window { // Retrieve buffer damage. let damage = data.damage.buffer(); - // Import and cache the buffer. - let action = match renderer.import_buffer(buffer, Some(surface_data), damage) { - Some(Ok(texture)) => { - // Release SHM buffers after import. - if let Some(BufferType::Shm) = renderer::buffer_type(buffer) { - data.buffer = None; - } - - // Update and cache the texture. - let texture = - Texture::from_surface(texture, self.scale, location, &data, surface); - let render_texture = RenderTexture::new(texture); - self.texture_cache.push(render_texture.clone(), location); - data.texture = Some(render_texture); - - TraversalAction::DoChildren(location) + let texture = match single_pixel_buffer::get_single_pixel_buffer(buffer) { + // Handle single-pixel buffer protocol. + Ok(buffer) => { + // Create 1x1 OpenGL texture. + let opaque = !buffer.has_alpha(); + let rgba = buffer.rgba8888(); + drawing::create_texture(renderer, &rgba, 1, 1, opaque) }, - _ => { - error!("unable to import buffer"); - data.buffer = None; + // Import and cache the buffer. + Err(_) => match renderer.import_buffer(buffer, Some(surface_data), damage) { + Some(Ok(texture)) => { + // Release SHM buffers after import. + if let Some(BufferType::Shm) = renderer::buffer_type(buffer) { + data.buffer = None; + } + + texture + }, + _ => { + error!("unable to import buffer"); + data.buffer = None; - TraversalAction::SkipChildren + return TraversalAction::SkipChildren; + }, }, }; + // Update and cache the texture. + let texture = Texture::from_surface(texture, self.scale, location, &data, surface); + let render_texture = RenderTexture::new(texture); + self.texture_cache.push(render_texture.clone(), location); + data.texture = Some(render_texture); + // Clear buffer damage after successful import. data.damage.clear(); - action + TraversalAction::DoChildren(location) }, |_, _, _| (), |_, _, _| true,