From cdfae58510895770c61ec3487b599d39aaf5338d Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 20 Dec 2022 14:24:29 -0800 Subject: [PATCH] wayland: Reuse buffers and pools; check buffer release Also updates `winit` example to redraw on resize, which seems to be necessary. With this resizing seems to be entirely smooth, without visual corruption from it overwriting the buffer the server is displaying. --- examples/winit.rs | 6 ++ src/wayland/buffer.rs | 137 +++++++++++++++++++++++++++++ src/{wayland.rs => wayland/mod.rs} | 120 +++++-------------------- 3 files changed, 165 insertions(+), 98 deletions(-) create mode 100644 src/wayland/buffer.rs rename src/{wayland.rs => wayland/mod.rs} (52%) diff --git a/examples/winit.rs b/examples/winit.rs index 8f26550..286d2b4 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -54,6 +54,12 @@ fn main() { } if window_id == window.id() => { *control_flow = ControlFlow::Exit; } + Event::WindowEvent { + event: WindowEvent::Resized(_), + window_id, + } if window_id == window.id() => { + window.request_redraw(); + } _ => {} } }); diff --git a/src/wayland/buffer.rs b/src/wayland/buffer.rs new file mode 100644 index 0000000..e38f1ac --- /dev/null +++ b/src/wayland/buffer.rs @@ -0,0 +1,137 @@ +use nix::sys::memfd::{memfd_create, MemFdCreateFlag}; +use std::{ + ffi::CStr, + fs::File, + os::unix::prelude::{AsRawFd, FileExt, FromRawFd}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use wayland_client::{ + protocol::{wl_buffer, wl_shm, wl_shm_pool, wl_surface}, + Connection, Dispatch, QueueHandle, +}; + +use super::State; + +pub(super) struct WaylandBuffer { + qh: QueueHandle, + tempfile: File, + pool: wl_shm_pool::WlShmPool, + pool_size: i32, + buffer: wl_buffer::WlBuffer, + width: i32, + height: i32, + released: Arc, +} + +impl WaylandBuffer { + pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle) -> Self { + let name = unsafe { CStr::from_bytes_with_nul_unchecked("swbuf\0".as_bytes()) }; + let tempfile_fd = memfd_create(name, MemFdCreateFlag::MFD_CLOEXEC) + .expect("Failed to create memfd to store buffer."); + let tempfile = unsafe { File::from_raw_fd(tempfile_fd) }; + let pool_size = width * height * 4; + let pool = shm.create_pool(tempfile.as_raw_fd(), pool_size, &qh, ()); + let released = Arc::new(AtomicBool::new(true)); + let buffer = pool.create_buffer( + 0, + width, + height, + width * 4, + wl_shm::Format::Xrgb8888, + &qh, + released.clone(), + ); + Self { + qh: qh.clone(), + tempfile, + pool, + pool_size, + buffer, + width, + height, + released, + } + } + + pub fn resize(&mut self, width: i32, height: i32) { + // If size is the same, there's nothing to do + if self.width != width || self.height != height { + // Destroy old buffer + self.buffer.destroy(); + + // Grow pool, if needed + let size = ((width * height * 4) as u32).next_power_of_two() as i32; + if size > self.pool_size { + let _ = self.tempfile.set_len(size as u64); + self.pool.resize(size); + self.pool_size = size; + } + + // Create buffer with correct size + self.buffer = self.pool.create_buffer( + 0, + width, + height, + width * 4, + wl_shm::Format::Xrgb8888, + &self.qh, + self.released.clone(), + ); + } + } + + pub fn write(&self, buffer: &[u32]) { + let buffer = + unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) }; + self.tempfile + .write_all_at(buffer, 0) + .expect("Failed to write buffer to temporary file."); + } + + pub fn attach(&self, surface: &wl_surface::WlSurface) { + self.released.store(false, Ordering::SeqCst); + surface.attach(Some(&self.buffer), 0, 0); + } + + pub fn released(&self) -> bool { + self.released.load(Ordering::SeqCst) + } +} + +impl Drop for WaylandBuffer { + fn drop(&mut self) { + self.buffer.destroy(); + self.pool.destroy(); + } +} + +impl Dispatch for State { + fn event( + _: &mut State, + _: &wl_shm_pool::WlShmPool, + _: wl_shm_pool::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch> for State { + fn event( + _: &mut State, + _: &wl_buffer::WlBuffer, + event: wl_buffer::Event, + released: &Arc, + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_buffer::Event::Release => released.store(true, Ordering::SeqCst), + _ => {} + } + } +} diff --git a/src/wayland.rs b/src/wayland/mod.rs similarity index 52% rename from src/wayland.rs rename to src/wayland/mod.rs index 581bd7a..147ecfd 100644 --- a/src/wayland.rs +++ b/src/wayland/mod.rs @@ -1,19 +1,15 @@ use crate::{error::unwrap, GraphicsContextImpl, SwBufError}; -use nix::sys::memfd::{memfd_create, MemFdCreateFlag}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; -use std::{ - ffi::CStr, - fs::File, - io::Write, - os::unix::prelude::{AsRawFd, FileExt, FromRawFd}, -}; use wayland_client::{ backend::{Backend, ObjectId}, globals::{registry_queue_init, GlobalListContents}, - protocol::{wl_buffer, wl_registry, wl_shm, wl_shm_pool, wl_surface}, + protocol::{wl_registry, wl_shm, wl_surface}, Connection, Dispatch, EventQueue, Proxy, QueueHandle, }; +mod buffer; +use buffer::WaylandBuffer; + struct State; pub struct WaylandImpl { @@ -21,22 +17,7 @@ pub struct WaylandImpl { qh: QueueHandle, surface: wl_surface::WlSurface, shm: wl_shm::WlShm, - tempfile: File, - buffer: Option, -} - -struct WaylandBuffer { - width: i32, - height: i32, - pool: wl_shm_pool::WlShmPool, - buffer: wl_buffer::WlBuffer, -} - -impl Drop for WaylandBuffer { - fn drop(&mut self) { - self.buffer.destroy(); - self.pool.destroy(); - } + buffers: Vec, } impl WaylandImpl { @@ -56,12 +37,6 @@ impl WaylandImpl { globals.bind(&qh, 1..=1, ()), "Failed to instantiate Wayland Shm", )?; - let name = CStr::from_bytes_with_nul_unchecked("swbuf\0".as_bytes()); - let tempfile_fd = unwrap( - memfd_create(name, MemFdCreateFlag::MFD_CLOEXEC), - "Failed to create temporary file to store buffer.", - )?; - let tempfile = File::from_raw_fd(tempfile_fd); let surface_id = unwrap( ObjectId::from_ptr( wl_surface::WlSurface::interface(), @@ -78,58 +53,31 @@ impl WaylandImpl { qh, surface, shm, - tempfile, - buffer: None, + buffers: Vec::new(), }) } - fn ensure_buffer_size(&mut self, width: i32, height: i32) { - if !self.check_buffer_size_equals(width, height) { - let pool = - self.shm - .create_pool(self.tempfile.as_raw_fd(), width * height * 4, &self.qh, ()); - let buffer = pool.create_buffer( - 0, - width, - height, - width * 4, - wayland_client::protocol::wl_shm::Format::Xrgb8888, - &self.qh, - (), - ); - self.buffer = Some(WaylandBuffer { - width, - height, - pool, - buffer, - }); - } - } - - fn check_buffer_size_equals(&self, width: i32, height: i32) -> bool { - match &self.buffer { - Some(buffer) => buffer.width == width && buffer.height == height, - None => false, + // Allocate or reuse a buffer of the given size + fn buffer(&mut self, width: i32, height: i32) -> &WaylandBuffer { + if let Some(idx) = self.buffers.iter().position(|i| i.released()) { + self.buffers[idx].resize(width, height); + &mut self.buffers[idx] + } else { + self.buffers + .push(WaylandBuffer::new(&self.shm, width, height, &self.qh)); + self.buffers.last().unwrap() } } } impl GraphicsContextImpl for WaylandImpl { unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { - self.ensure_buffer_size(width as i32, height as i32); - let wayland_buffer = self.buffer.as_mut().unwrap(); - self.tempfile.set_len(buffer.len() as u64 * 4) - .expect("Failed to truncate temporary file."); - self.tempfile - .write_at( - std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4), - 0, - ) - .expect("Failed to write buffer to temporary file."); - self.tempfile - .flush() - .expect("Failed to flush buffer to temporary file."); - self.surface.attach(Some(&wayland_buffer.buffer), 0, 0); + let _ = self.event_queue.dispatch_pending(&mut State); + + let surface = self.surface.clone(); + let wayland_buffer = self.buffer(width.into(), height.into()); + wayland_buffer.write(buffer); + wayland_buffer.attach(&surface); // FIXME: Proper damaging mechanism. // @@ -145,8 +93,8 @@ impl GraphicsContextImpl for WaylandImpl { self.surface .damage_buffer(0, 0, width as i32, height as i32); } - self.surface.commit(); + let _ = self.event_queue.flush(); } } @@ -175,27 +123,3 @@ impl Dispatch for State { ) { } } - -impl Dispatch for State { - fn event( - _: &mut State, - _: &wl_shm_pool::WlShmPool, - _: wl_shm_pool::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - _: &mut State, - _: &wl_buffer::WlBuffer, - _: wl_buffer::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -}