Skip to content

Commit

Permalink
Implement single-pixel buffer protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisduerr committed Jan 20, 2024
1 parent 839b393 commit 595eb4d
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 57 deletions.
10 changes: 9 additions & 1 deletion src/catacomb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -220,6 +224,8 @@ impl Catacomb {
let clock_id = libc::CLOCK_MONOTONIC as u32;
PresentationState::new::<Self>(&display_handle, clock_id);

SinglePixelBufferState::new::<Self>(&display_handle);

// Initialize idle-inhibit protocol.
IdleInhibitManagerState::new::<Self>(&display_handle);

Expand Down Expand Up @@ -850,3 +856,5 @@ impl ClientData for ClientState {

fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}

delegate_single_pixel_buffer!(Catacomb);
89 changes: 55 additions & 34 deletions src/drawing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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];
Expand Down Expand Up @@ -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::<i32, Physical>::from((width, height)).to_f64().to_logical(scale).to_i32_round();
Texture::new(texture, logical_size, scale, opaque)
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<i32, Logical> {
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 {
Expand All @@ -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) }
}
1 change: 1 addition & 0 deletions src/protocols/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod idle_notify;
pub mod screencopy;
pub mod single_pixel_buffer;
82 changes: 82 additions & 0 deletions src/protocols/single_pixel_buffer/handlers.rs
Original file line number Diff line number Diff line change
@@ -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<D> GlobalDispatch<WpSinglePixelBufferManagerV1, (), D> for SinglePixelBufferState
where
D: GlobalDispatch<WpSinglePixelBufferManagerV1, ()>,
D: Dispatch<WpSinglePixelBufferManagerV1, ()>,
D: 'static,
{
fn bind(
_state: &mut D,
_dh: &DisplayHandle,
_client: &Client,
resource: New<WpSinglePixelBufferManagerV1>,
_global_data: &(),
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, ());
}
}

impl<D> Dispatch<WpSinglePixelBufferManagerV1, (), D> for SinglePixelBufferState
where
D: Dispatch<WpSinglePixelBufferManagerV1, ()>,
D: Dispatch<WlBuffer, SinglePixelBufferUserData>,
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<D> Dispatch<WlBuffer, SinglePixelBufferUserData, D> for SinglePixelBufferState
where
D: Dispatch<WlBuffer, SinglePixelBufferUserData>,
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!(),
}
}
}
92 changes: 92 additions & 0 deletions src/protocols/single_pixel_buffer/mod.rs
Original file line number Diff line number Diff line change
@@ -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<D>(display: &DisplayHandle) -> Self
where
D: GlobalDispatch<WpSinglePixelBufferManagerV1, ()>,
D: Dispatch<WpSinglePixelBufferManagerV1, ()>,
D: 'static,
{
display.create_global::<D, WpSinglePixelBufferManagerV1, _>(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::<SinglePixelBufferUserData>().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);
};
}
Loading

0 comments on commit 595eb4d

Please sign in to comment.