Owned pixel buffer for no-copy presentation
This is based on the API that will be used for no-copy presentation. But wraps it in `set_buffer`. This also fixes the Wayland buffer code to set `self.width` and `self.height` on resize, and set the length of the shared memory file when the buffer is created. Co-authored-by: jtnunley <jtnunley01@gmail.com>
This commit is contained in:
parent
e5d546ff9e
commit
a09e4cf679
19 changed files with 1176 additions and 438 deletions
389
src/x11.rs
389
src/x11.rs
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use crate::SoftBufferError;
|
||||
use crate::{util, SoftBufferError};
|
||||
use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID};
|
||||
use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle};
|
||||
use std::ptr::{null_mut, NonNull};
|
||||
use std::{fmt, io, sync::Arc};
|
||||
use std::{fmt, io, mem, num::NonZeroU32, rc::Rc};
|
||||
|
||||
use x11_dl::xlib::Display;
|
||||
use x11_dl::xlib_xcb::Xlib_xcb;
|
||||
|
|
@ -80,6 +80,9 @@ impl X11DisplayImpl {
|
|||
};
|
||||
|
||||
let is_shm_available = is_shm_available(&connection);
|
||||
if !is_shm_available {
|
||||
log::warn!("SHM extension is not available. Performance may be poor.");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
connection,
|
||||
|
|
@ -91,7 +94,7 @@ impl X11DisplayImpl {
|
|||
/// The handle to an X11 drawing context.
|
||||
pub struct X11Impl {
|
||||
/// X display this window belongs to.
|
||||
display: Arc<X11DisplayImpl>,
|
||||
display: Rc<X11DisplayImpl>,
|
||||
|
||||
/// The window to draw to.
|
||||
window: xproto::Window,
|
||||
|
|
@ -102,11 +105,26 @@ pub struct X11Impl {
|
|||
/// The depth (bits per pixel) of the drawing context.
|
||||
depth: u8,
|
||||
|
||||
/// Information about SHM, if it is available.
|
||||
shm: Option<ShmInfo>,
|
||||
/// The buffer we draw to.
|
||||
buffer: Buffer,
|
||||
|
||||
/// The current buffer width.
|
||||
width: u16,
|
||||
|
||||
/// The current buffer height.
|
||||
height: u16,
|
||||
}
|
||||
|
||||
struct ShmInfo {
|
||||
/// The buffer that is being drawn to.
|
||||
enum Buffer {
|
||||
/// A buffer implemented using shared memory to prevent unnecessary copying.
|
||||
Shm(ShmBuffer),
|
||||
|
||||
/// A normal buffer that we send over the wire.
|
||||
Wire(Vec<u32>),
|
||||
}
|
||||
|
||||
struct ShmBuffer {
|
||||
/// The shared memory segment, paired with its ID.
|
||||
seg: Option<(ShmSegment, shm::Seg)>,
|
||||
|
||||
|
|
@ -133,7 +151,7 @@ impl X11Impl {
|
|||
/// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid.
|
||||
pub unsafe fn from_xlib(
|
||||
window_handle: XlibWindowHandle,
|
||||
display: Arc<X11DisplayImpl>,
|
||||
display: Rc<X11DisplayImpl>,
|
||||
) -> Result<Self, SoftBufferError> {
|
||||
let mut xcb_window_handle = XcbWindowHandle::empty();
|
||||
xcb_window_handle.window = window_handle.window as _;
|
||||
|
|
@ -150,8 +168,10 @@ impl X11Impl {
|
|||
/// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid.
|
||||
pub(crate) unsafe fn from_xcb(
|
||||
window_handle: XcbWindowHandle,
|
||||
display: Arc<X11DisplayImpl>,
|
||||
display: Rc<X11DisplayImpl>,
|
||||
) -> Result<Self, SoftBufferError> {
|
||||
log::trace!("new: window_handle={:X}", window_handle.window,);
|
||||
|
||||
// Check that the handle is valid.
|
||||
if window_handle.window == 0 {
|
||||
return Err(SoftBufferError::IncompleteWindowHandle);
|
||||
|
|
@ -187,18 +207,15 @@ impl X11Impl {
|
|||
.swbuf_err("Failed to get geometry reply")?;
|
||||
|
||||
// See if SHM is available.
|
||||
let shm_info = {
|
||||
let present = display.is_shm_available;
|
||||
|
||||
if present {
|
||||
// SHM is available.
|
||||
Some(ShmInfo {
|
||||
seg: None,
|
||||
done_processing: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let buffer = if display.is_shm_available {
|
||||
// SHM is available.
|
||||
Buffer::Shm(ShmBuffer {
|
||||
seg: None,
|
||||
done_processing: None,
|
||||
})
|
||||
} else {
|
||||
// SHM is not available.
|
||||
Buffer::Wire(Vec::new())
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -206,119 +223,184 @@ impl X11Impl {
|
|||
window,
|
||||
gc,
|
||||
depth: geometry_reply.depth,
|
||||
shm: shm_info,
|
||||
buffer,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
|
||||
// Draw the image to the buffer.
|
||||
let result = unsafe { self.set_buffer_shm(buffer, width, height) }.and_then(|had_shm| {
|
||||
if had_shm {
|
||||
Ok(())
|
||||
} else {
|
||||
log::debug!("Falling back to non-SHM method");
|
||||
self.set_buffer_fallback(buffer, width, height)
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!("Failed to draw image to window: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Put the given buffer into the window using the SHM method.
|
||||
///
|
||||
/// Returns `false` if SHM is not available.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The buffer's length must be `width * height`.
|
||||
unsafe fn set_buffer_shm(
|
||||
/// Resize the internal buffer to the given width and height.
|
||||
pub(crate) fn resize(
|
||||
&mut self,
|
||||
buffer: &[u32],
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Result<bool, PushBufferError> {
|
||||
let shm_info = match self.shm {
|
||||
Some(ref mut info) => info,
|
||||
None => return Ok(false),
|
||||
};
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<(), SoftBufferError> {
|
||||
log::trace!(
|
||||
"resize: window={:X}, size={}x{}",
|
||||
self.window,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
// If the X server is still processing the last image, wait for it to finish.
|
||||
shm_info.finish_wait(&self.display.connection)?;
|
||||
// Width and height should fit in u16.
|
||||
let width: u16 = width
|
||||
.get()
|
||||
.try_into()
|
||||
.or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
|
||||
let height: u16 = height
|
||||
.get()
|
||||
.try_into()
|
||||
.or(Err(SoftBufferError::SizeOutOfRange {
|
||||
width: NonZeroU32::new(width.into()).unwrap(),
|
||||
height,
|
||||
}))?;
|
||||
|
||||
// Get the SHM segment to use.
|
||||
let necessary_size = (width as usize) * (height as usize) * 4;
|
||||
let (segment, segment_id) = shm_info.segment(&self.display.connection, necessary_size)?;
|
||||
if width != self.width || height != self.height {
|
||||
self.buffer
|
||||
.resize(&self.display.connection, width, height)
|
||||
.swbuf_err("Failed to resize X11 buffer")?;
|
||||
|
||||
// Copy the buffer into the segment.
|
||||
// SAFETY: The buffer is properly sized and we've ensured that the X server isn't reading from it.
|
||||
unsafe {
|
||||
segment.copy(buffer);
|
||||
// We successfully resized the buffer.
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
|
||||
// Put the image into the window.
|
||||
self.display
|
||||
.connection
|
||||
.shm_put_image(
|
||||
self.window,
|
||||
self.gc,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
self.depth,
|
||||
xproto::ImageFormat::Z_PIXMAP.into(),
|
||||
false,
|
||||
segment_id,
|
||||
0,
|
||||
)?
|
||||
.ignore_error();
|
||||
|
||||
// Send a short request to act as a notification for when the X server is done processing the image.
|
||||
shm_info.begin_wait(&self.display.connection)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Put the given buffer into the window using the fallback wire transfer method.
|
||||
fn set_buffer_fallback(
|
||||
&mut self,
|
||||
buffer: &[u32],
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Result<(), PushBufferError> {
|
||||
self.display
|
||||
.connection
|
||||
.put_image(
|
||||
xproto::ImageFormat::Z_PIXMAP,
|
||||
self.window,
|
||||
self.gc,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
self.depth,
|
||||
bytemuck::cast_slice(buffer),
|
||||
)?
|
||||
.ignore_error();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the buffer.
|
||||
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
|
||||
log::trace!("buffer_mut: window={:X}", self.window);
|
||||
|
||||
Ok(BufferImpl(util::BorrowStack::new(self, |surface| {
|
||||
let buffer = surface
|
||||
.buffer
|
||||
.buffer_mut(&surface.display.connection)
|
||||
.swbuf_err("Failed to get mutable X11 buffer")?;
|
||||
|
||||
// Crop it down to the window size.
|
||||
Ok(&mut buffer[..total_len(surface.width, surface.height) / 4])
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmInfo {
|
||||
pub struct BufferImpl<'a>(util::BorrowStack<'a, X11Impl, [u32]>);
|
||||
|
||||
impl<'a> BufferImpl<'a> {
|
||||
#[inline]
|
||||
pub fn pixels(&self) -> &[u32] {
|
||||
self.0.member()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pixels_mut(&mut self) -> &mut [u32] {
|
||||
self.0.member_mut()
|
||||
}
|
||||
|
||||
/// Push the buffer to the window.
|
||||
pub fn present(self) -> Result<(), SoftBufferError> {
|
||||
let imp = self.0.into_container();
|
||||
|
||||
log::trace!("present: window={:X}", imp.window);
|
||||
|
||||
let result = match imp.buffer {
|
||||
Buffer::Wire(ref wire) => {
|
||||
// This is a suboptimal strategy, raise a stink in the debug logs.
|
||||
log::debug!("Falling back to non-SHM method for window drawing.");
|
||||
|
||||
imp.display
|
||||
.connection
|
||||
.put_image(
|
||||
xproto::ImageFormat::Z_PIXMAP,
|
||||
imp.window,
|
||||
imp.gc,
|
||||
imp.width,
|
||||
imp.height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
imp.depth,
|
||||
bytemuck::cast_slice(wire),
|
||||
)
|
||||
.map(|c| c.ignore_error())
|
||||
.push_err()
|
||||
}
|
||||
|
||||
Buffer::Shm(ref mut shm) => {
|
||||
// If the X server is still processing the last image, wait for it to finish.
|
||||
shm.finish_wait(&imp.display.connection)
|
||||
.and_then(|()| {
|
||||
// Put the image into the window.
|
||||
if let Some((_, segment_id)) = shm.seg {
|
||||
imp.display
|
||||
.connection
|
||||
.shm_put_image(
|
||||
imp.window,
|
||||
imp.gc,
|
||||
imp.width,
|
||||
imp.height,
|
||||
0,
|
||||
0,
|
||||
imp.width,
|
||||
imp.height,
|
||||
0,
|
||||
0,
|
||||
imp.depth,
|
||||
xproto::ImageFormat::Z_PIXMAP.into(),
|
||||
false,
|
||||
segment_id,
|
||||
0,
|
||||
)
|
||||
.push_err()
|
||||
.map(|c| c.ignore_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.and_then(|()| {
|
||||
// Send a short request to act as a notification for when the X server is done processing the image.
|
||||
shm.begin_wait(&imp.display.connection)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
result.swbuf_err("Failed to draw image to window")
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Resize the buffer to the given size.
|
||||
fn resize(
|
||||
&mut self,
|
||||
conn: &impl Connection,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Result<(), PushBufferError> {
|
||||
match self {
|
||||
Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
|
||||
Buffer::Wire(wire) => {
|
||||
wire.resize(total_len(width, height), 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the buffer.
|
||||
fn buffer_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
|
||||
match self {
|
||||
Buffer::Shm(ref mut shm) => shm.as_mut(conn),
|
||||
Buffer::Wire(wire) => Ok(wire),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmBuffer {
|
||||
/// Allocate a new `ShmSegment` of the given size.
|
||||
fn segment(
|
||||
fn alloc_segment(
|
||||
&mut self,
|
||||
conn: &impl Connection,
|
||||
size: usize,
|
||||
) -> Result<(&mut ShmSegment, shm::Seg), PushBufferError> {
|
||||
) -> Result<(), PushBufferError> {
|
||||
// Round the size up to the next power of two to prevent frequent reallocations.
|
||||
let size = size.next_power_of_two();
|
||||
|
||||
|
|
@ -334,12 +416,25 @@ impl ShmInfo {
|
|||
self.associate(conn, new_seg)?;
|
||||
}
|
||||
|
||||
// Get the segment and ID.
|
||||
Ok(self
|
||||
.seg
|
||||
.as_mut()
|
||||
.map(|(ref mut seg, id)| (seg, *id))
|
||||
.unwrap())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the SHM buffer as a mutable reference.
|
||||
fn as_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
|
||||
// Make sure that, if we're waiting for the X server to finish processing the last image,
|
||||
// that we finish the wait.
|
||||
self.finish_wait(conn)?;
|
||||
|
||||
match self.seg.as_mut() {
|
||||
Some((seg, _)) => {
|
||||
// SAFETY: No other code should be able to access the segment.
|
||||
Ok(bytemuck::cast_slice_mut(unsafe { seg.as_mut() }))
|
||||
}
|
||||
None => {
|
||||
// Nothing has been allocated yet.
|
||||
Ok(&mut [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate an SHM segment with the server.
|
||||
|
|
@ -354,6 +449,9 @@ impl ShmInfo {
|
|||
|
||||
// Take out the old one and detach it.
|
||||
if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
|
||||
// Wait for the old segment to finish processing.
|
||||
self.finish_wait(conn)?;
|
||||
|
||||
conn.shm_detach(old_id)?.ignore_error();
|
||||
|
||||
// Drop the old segment.
|
||||
|
|
@ -415,23 +513,13 @@ impl ShmSegment {
|
|||
}
|
||||
}
|
||||
|
||||
/// Copy data into this shared memory segment.
|
||||
/// Get this shared memory segment as a mutable reference.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes that the size of `self`'s buffer is larger than or equal to `data.len()`.
|
||||
/// In addition, no other processes should be reading from or writing to this memory.
|
||||
unsafe fn copy<T: bytemuck::NoUninit>(&mut self, data: &[T]) {
|
||||
debug_assert!(data.len() * std::mem::size_of::<T>() <= self.size,);
|
||||
let incoming_data = bytemuck::cast_slice::<_, u8>(data);
|
||||
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
incoming_data.as_ptr(),
|
||||
self.ptr.as_ptr() as *mut u8,
|
||||
incoming_data.len(),
|
||||
)
|
||||
}
|
||||
/// One must ensure that no other processes are reading from or writing to this memory.
|
||||
unsafe fn as_mut(&mut self) -> &mut [i8] {
|
||||
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
|
||||
}
|
||||
|
||||
/// Get the size of this shared memory segment.
|
||||
|
|
@ -460,7 +548,7 @@ impl Drop for ShmSegment {
|
|||
impl Drop for X11Impl {
|
||||
fn drop(&mut self) {
|
||||
// If we used SHM, make sure it's detached from the server.
|
||||
if let Some(mut shm) = self.shm.take() {
|
||||
if let Buffer::Shm(mut shm) = mem::replace(&mut self.buffer, Buffer::Wire(Vec::new())) {
|
||||
// If we were in the middle of processing a buffer, wait for it to finish.
|
||||
shm.finish_wait(&self.display.connection).ok();
|
||||
|
||||
|
|
@ -563,11 +651,11 @@ impl From<io::Error> for PushBufferError {
|
|||
}
|
||||
|
||||
/// Convenient wrapper to cast errors into SoftBufferError.
|
||||
trait ResultExt<T, E> {
|
||||
trait SwResultExt<T, E> {
|
||||
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError>;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
|
||||
impl<T, E: std::error::Error + 'static> SwResultExt<T, E> for Result<T, E> {
|
||||
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> {
|
||||
self.map_err(|e| {
|
||||
SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e))))
|
||||
|
|
@ -575,6 +663,17 @@ impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenient wrapper to cast errors into PushBufferError.
|
||||
trait PushResultExt<T, E> {
|
||||
fn push_err(self) -> Result<T, PushBufferError>;
|
||||
}
|
||||
|
||||
impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
|
||||
fn push_err(self) -> Result<T, PushBufferError> {
|
||||
self.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a library error.
|
||||
///
|
||||
/// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast
|
||||
|
|
@ -594,3 +693,15 @@ impl<E: fmt::Display> fmt::Display for LibraryError<E> {
|
|||
}
|
||||
|
||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
|
||||
|
||||
/// Get the length that a slice needs to be to hold a buffer of the given dimensions.
|
||||
#[inline(always)]
|
||||
fn total_len(width: u16, height: u16) -> usize {
|
||||
let width: usize = width.into();
|
||||
let height: usize = height.into();
|
||||
|
||||
width
|
||||
.checked_mul(height)
|
||||
.and_then(|len| len.checked_mul(4))
|
||||
.unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue