Files
wldash/src/doublemempool.rs
T

266 lines
8.5 KiB
Rust

use std::{
cell::RefCell,
ffi::CStr,
fs::File,
io,
os::unix::io::{FromRawFd, RawFd},
rc::Rc,
time::SystemTime,
time::UNIX_EPOCH,
};
#[cfg(target_os = "linux")]
use nix::sys::memfd;
use nix::{
errno::Errno,
fcntl,
sys::{mman, stat},
unistd,
};
use memmap::MmapMut;
use wayland_client::{
protocol::{wl_buffer, wl_shm, wl_shm_pool},
Main,
};
/// A wrapper handling an SHM memory pool backed by a shared memory file
///
/// This wrapper handles for you the creation of the shared memory file and its synchronization
/// with the protocol.
///
/// Mempool internally tracks the release of the buffers by the compositor. As such, creating a buffer
/// that is not commited to a surface (and then never released by the server) would cause the Mempool
/// to be stuck believing it is still in use.
///
/// Mempool will also handle the destruction of buffers and as such the `destroy()` method should not
/// be used on buffers created from Mempool.
///
/// Overwriting the contents of the memory pool before it is completely freed may cause graphical
/// glitches due to the possible corruption of data while the compositor is reading it.
///
/// Mempool requires a callback that will be called when the pool becomes free, this
/// happens when all the pools buffers are released by the server.
pub struct MemPool {
file: File,
len: usize,
pool: Main<wl_shm_pool::WlShmPool>,
buffer_count: Rc<RefCell<u32>>,
mmap: MmapMut,
callback: Rc<RefCell<dyn FnMut(wayland_client::DispatchData)>>,
}
impl MemPool {
/// Create a new memory pool associated with given shm
pub fn new<F>(shm: Main<wl_shm::WlShm>, callback: F) -> io::Result<MemPool>
where
F: FnMut(wayland_client::DispatchData) + 'static,
{
let mem_fd = create_shm_fd()?;
let mem_file = unsafe { File::from_raw_fd(mem_fd) };
mem_file.set_len(128)?;
let pool = shm.create_pool(mem_fd, 128);
let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() };
Ok(MemPool {
file: mem_file,
len: 128,
pool,
buffer_count: Rc::new(RefCell::new(0)),
mmap,
callback: Rc::new(RefCell::new(callback)),
})
}
/// Resize the memory pool
///
/// This affect the size as it is seen by the wayland server. Even
/// if you extend the temporary file size by writing to it, you need to
/// call this method otherwise the server won't see the new size.
///
/// Memory pools can only be extented, as such this method will do nothing
/// if the requested new size is smaller than the current size.
///
/// This method allows you to ensure the underlying pool is large enough to
/// hold what you want to write to it.
pub fn resize(&mut self, newsize: usize) -> io::Result<()> {
if newsize > self.len {
self.file.set_len(newsize as u64)?;
self.pool.resize(newsize as i32);
self.len = newsize;
self.mmap = unsafe { MmapMut::map_mut(&self.file).unwrap() };
}
Ok(())
}
/// Create a new buffer to this pool
///
/// The parameters are:
///
/// - `offset`: the offset (in bytes) from the beginning of the pool at which this
/// buffer starts
/// - `width`: the width of this buffer (in pixels)
/// - `height`: the height of this buffer (in pixels)
/// - `stride`: distance (in bytes) between the beginning of a row and the next one
/// - `format`: the encoding format of the pixels. Using a format that was not
/// advertised to the `wl_shm` global by the server is a protocol error and will
/// terminate your connection
pub fn buffer(
&self,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: wl_shm::Format,
) -> wl_buffer::WlBuffer {
*self.buffer_count.borrow_mut() += 1;
let my_buffer_count = self.buffer_count.clone();
let my_callback = self.callback.clone();
let buffer = self
.pool
.create_buffer(offset, width, height, stride, format);
buffer.quick_assign(move |buffer, event, dispatch_data| match event {
wl_buffer::Event::Release => {
buffer.destroy();
let new_count = {
// borrow the buffer_count for as short as possible, in case
// the user wants to create a new buffer from the callback
let mut my_buffer_count = my_buffer_count.borrow_mut();
*my_buffer_count -= 1;
*my_buffer_count
};
if new_count == 0 {
(&mut *my_callback.borrow_mut())(dispatch_data);
}
}
_ => unreachable!(),
});
(*buffer).clone().detach()
}
/// Uses the memmap crate to map the underlying shared memory file
pub fn mmap(&mut self) -> &mut MmapMut {
&mut self.mmap
}
/// Returns true if the pool contains buffers that are currently in use by the server
pub fn is_used(&self) -> bool {
*self.buffer_count.borrow() != 0
}
}
impl Drop for MemPool {
fn drop(&mut self) {
self.pool.destroy();
}
}
impl io::Write for MemPool {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.file, buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.file)
}
}
impl io::Seek for MemPool {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.file, pos)
}
}
fn create_shm_fd() -> io::Result<RawFd> {
// Only try memfd on linux
#[cfg(target_os = "linux")]
loop {
match memfd::memfd_create(
CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap(),
memfd::MemFdCreateFlag::MFD_CLOEXEC,
) {
Ok(fd) => return Ok(fd),
Err(nix::Error::Sys(Errno::EINTR)) => continue,
Err(nix::Error::Sys(Errno::ENOSYS)) => break,
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => unreachable!(err),
}
}
// Fallback to using shm_open
let sys_time = SystemTime::now();
let mut mem_file_handle = format!(
"/smithay-client-toolkit-{}",
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
loop {
match mman::shm_open(
mem_file_handle.as_str(),
fcntl::OFlag::O_CREAT
| fcntl::OFlag::O_EXCL
| fcntl::OFlag::O_RDWR
| fcntl::OFlag::O_CLOEXEC,
stat::Mode::S_IRUSR | stat::Mode::S_IWUSR,
) {
Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) {
Ok(_) => return Ok(fd),
Err(nix::Error::Sys(errno)) => match unistd::close(fd) {
Ok(_) => return Err(io::Error::from(errno)),
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => panic!("{}", err),
},
Err(err) => panic!("{}", err),
},
Err(nix::Error::Sys(Errno::EEXIST)) => {
// If a file with that handle exists then change the handle
mem_file_handle = format!(
"/smithay-client-toolkit-{}",
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
continue;
}
Err(nix::Error::Sys(Errno::EINTR)) => continue,
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => unreachable!(err),
}
}
}
pub struct DoubleMemPool {
pool1: MemPool,
pool2: MemPool,
switch: bool,
}
impl DoubleMemPool {
pub fn new(shm: Main<wl_shm::WlShm>) -> ::std::io::Result<DoubleMemPool> {
Ok(DoubleMemPool {
pool1: MemPool::new(shm.clone(), move |_| {})?,
pool2: MemPool::new(shm.clone(), move |_| {})?,
switch: false,
})
}
pub fn pool(&mut self) -> Option<(&mut MemPool, &mut MemPool)> {
let switch = self.switch;
self.switch = !self.switch;
let (last, cur) = if !switch {
(&mut self.pool2, &mut self.pool1)
} else {
(&mut self.pool1, &mut self.pool2)
};
if cur.is_used() {
None
} else {
Some((last, cur))
}
}
pub fn never_mind(&mut self) {
self.switch = !self.switch;
}
}