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.
This commit is contained in:
Ian Douglas Scott 2022-12-20 14:24:29 -08:00 committed by Jeremy Soller
parent 9b8641fc07
commit cdfae58510
3 changed files with 165 additions and 98 deletions

View file

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

137
src/wayland/buffer.rs Normal file
View file

@ -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<State>,
tempfile: File,
pool: wl_shm_pool::WlShmPool,
pool_size: i32,
buffer: wl_buffer::WlBuffer,
width: i32,
height: i32,
released: Arc<AtomicBool>,
}
impl WaylandBuffer {
pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle<State>) -> 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<wl_shm_pool::WlShmPool, ()> for State {
fn event(
_: &mut State,
_: &wl_shm_pool::WlShmPool,
_: wl_shm_pool::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
}
}
impl Dispatch<wl_buffer::WlBuffer, Arc<AtomicBool>> for State {
fn event(
_: &mut State,
_: &wl_buffer::WlBuffer,
event: wl_buffer::Event,
released: &Arc<AtomicBool>,
_: &Connection,
_: &QueueHandle<State>,
) {
match event {
wl_buffer::Event::Release => released.store(true, Ordering::SeqCst),
_ => {}
}
}
}

View file

@ -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<State>,
surface: wl_surface::WlSurface,
shm: wl_shm::WlShm,
tempfile: File,
buffer: Option<WaylandBuffer>,
}
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<WaylandBuffer>,
}
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<wl_shm::WlShm, ()> for State {
) {
}
}
impl Dispatch<wl_shm_pool::WlShmPool, ()> for State {
fn event(
_: &mut State,
_: &wl_shm_pool::WlShmPool,
_: wl_shm_pool::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
}
}
impl Dispatch<wl_buffer::WlBuffer, ()> for State {
fn event(
_: &mut State,
_: &wl_buffer::WlBuffer,
_: wl_buffer::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
}
}