Skip to content

Commit

Permalink
wayland: Reuse buffers and pools; check buffer release
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ids1024 authored and jackpot51 committed Dec 20, 2022
1 parent 9b8641f commit cdfae58
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 98 deletions.
6 changes: 6 additions & 0 deletions examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
_ => {}
}
});
Expand Down
137 changes: 137 additions & 0 deletions src/wayland/buffer.rs
Original file line number Diff line number Diff line change
@@ -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),
_ => {}
}
}
}
120 changes: 22 additions & 98 deletions src/wayland.rs → src/wayland/mod.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
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 {
event_queue: EventQueue<State>,
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 {
Expand All @@ -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(),
Expand All @@ -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.
//
Expand All @@ -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();
}
}
Expand Down Expand Up @@ -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>,
) {
}
}

0 comments on commit cdfae58

Please sign in to comment.