Further optimization passes
This commit is contained in:
parent
03b73066cb
commit
104261d74c
3 changed files with 353 additions and 178 deletions
123
src/main.rs
123
src/main.rs
|
|
@ -806,97 +806,94 @@ fn render_frame(data: &mut EventLoopData) {
|
|||
}).collect()
|
||||
});
|
||||
|
||||
// Prepare memfd pair for this window (ping-pong double buffer)
|
||||
let pair = data.state.window_shm_buffers
|
||||
.entry(window_name.clone())
|
||||
.or_insert_with(|| {
|
||||
let a = nix::sys::memfd::memfd_create(
|
||||
std::ffi::CStr::from_bytes_with_nul(b"wayland_window\0").unwrap(),
|
||||
MFdFlags::MFD_CLOEXEC,
|
||||
).expect("creating memfd A");
|
||||
let b = nix::sys::memfd::memfd_create(
|
||||
std::ffi::CStr::from_bytes_with_nul(b"wayland_window\0").unwrap(),
|
||||
MFdFlags::MFD_CLOEXEC,
|
||||
).expect("creating memfd B");
|
||||
[a, b]
|
||||
});
|
||||
let idx = data.state.window_shm_write_idx
|
||||
.entry(window_name.clone())
|
||||
.or_insert(0);
|
||||
let memfd_fd = &pair[*idx];
|
||||
|
||||
// Try SHM buffer: write directly to memfd (no intermediate Vec)
|
||||
let shm_meta: Option<(u32, u32, u32)> = with_states(surface, |states| {
|
||||
// Try SHM buffer: write directly to mmap'd memfd with BGRA→RGBA conversion
|
||||
let shm_meta: Option<(u32, u32, u32, i32)> = with_states(surface, |states| {
|
||||
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>()?;
|
||||
let rss = rss_data.lock().ok()?;
|
||||
let buffer = rss.buffer()?;
|
||||
|
||||
let mut result: Option<(u32, u32, u32)> = None;
|
||||
let mut result: Option<(u32, u32, u32, i32)> = None;
|
||||
let _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
||||
let pixel_bytes =
|
||||
(spec.stride.max(0) * spec.height.max(0)) as usize;
|
||||
let src_stride = spec.stride.max(0) as usize;
|
||||
let bw = spec.width.max(0) as u32;
|
||||
let bh = spec.height.max(0) as u32;
|
||||
let pixel_bytes = src_stride * bh as usize;
|
||||
if pixel_bytes == 0 || pixel_bytes > len { return; }
|
||||
|
||||
let slice =
|
||||
unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
||||
let dst_stride = (bw as usize) * 4;
|
||||
let dst_size = dst_stride * bh as usize;
|
||||
|
||||
// Ensure memfd is correctly sized
|
||||
let size = pixel_bytes as i64;
|
||||
let cur_size = nix::sys::stat::fstat(memfd_fd)
|
||||
.map(|s| s.st_size).unwrap_or(0);
|
||||
if cur_size != size {
|
||||
let _ = nix::unistd::ftruncate(memfd_fd, size);
|
||||
let pair = data.state.window_shm_slots
|
||||
.entry(window_name.clone())
|
||||
.or_insert_with(|| {
|
||||
crate::state::ShmSlotPair::new(dst_size)
|
||||
.expect("creating ShmSlotPair")
|
||||
});
|
||||
|
||||
let slot = pair.write_slot_mut();
|
||||
if let Err(e) = slot.ensure_size(dst_size) {
|
||||
warn!("Failed to resize SHM slot for {}: {}", window_name, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Partial-damage write: when damage rects cover a small
|
||||
// area and the memfd was already the correct size (meaning
|
||||
// the previous full frame is still valid for undamaged
|
||||
// regions), write only the damaged rows.
|
||||
let usable_damage = !damage_rects.is_empty()
|
||||
&& damage_rects.len() < 64
|
||||
&& cur_size == size;
|
||||
let dst = unsafe { slot.as_mut_slice() };
|
||||
let src = unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
||||
|
||||
if usable_damage {
|
||||
let stride = spec.stride as usize;
|
||||
let height = spec.height as usize;
|
||||
let do_partial = !damage_rects.is_empty()
|
||||
&& damage_rects.len() < 64;
|
||||
|
||||
if do_partial {
|
||||
for rect in &damage_rects {
|
||||
let y0 = (rect.y.max(0) as usize).min(height);
|
||||
let y1 = ((rect.y + rect.height).max(0) as usize).min(height);
|
||||
if y0 >= y1 { continue; }
|
||||
let byte_start = y0 * stride;
|
||||
let byte_end = y1 * stride;
|
||||
let _ = nix::unistd::lseek(
|
||||
memfd_fd, byte_start as i64,
|
||||
nix::unistd::Whence::SeekSet,
|
||||
);
|
||||
let _ = nix::unistd::write(memfd_fd, &slice[byte_start..byte_end]);
|
||||
let rx = (rect.x as usize).min(bw as usize);
|
||||
let ry = (rect.y as usize).min(bh as usize);
|
||||
let rw = (rect.width as usize).min(bw as usize - rx);
|
||||
let rh = (rect.height as usize).min(bh as usize - ry);
|
||||
for row in ry..ry + rh {
|
||||
let src_row_off = row * src_stride + rx * 4;
|
||||
let dst_row_off = row * dst_stride + rx * 4;
|
||||
for col in 0..rw {
|
||||
let si = src_row_off + col * 4;
|
||||
let di = dst_row_off + col * 4;
|
||||
dst[di] = src[si + 2]; // R
|
||||
dst[di + 1] = src[si + 1]; // G
|
||||
dst[di + 2] = src[si]; // B
|
||||
dst[di + 3] = src[si + 3]; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = nix::unistd::lseek(memfd_fd, 0, nix::unistd::Whence::SeekSet);
|
||||
let _ = nix::unistd::write(memfd_fd, slice);
|
||||
for row in 0..bh as usize {
|
||||
let src_row_off = row * src_stride;
|
||||
let dst_row_off = row * dst_stride;
|
||||
for col in 0..bw as usize {
|
||||
let si = src_row_off + col * 4;
|
||||
let di = dst_row_off + col * 4;
|
||||
dst[di] = src[si + 2]; // R
|
||||
dst[di + 1] = src[si + 1]; // G
|
||||
dst[di + 2] = src[si]; // B
|
||||
dst[di + 3] = src[si + 3]; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = Some((
|
||||
spec.width as u32,
|
||||
spec.height as u32,
|
||||
spec.stride as u32,
|
||||
));
|
||||
// Get the fd of the slot we just wrote to (write_slot_mut already flipped)
|
||||
let wrote_idx = 1 - pair.current_write_idx();
|
||||
let memfd_raw = std::os::unix::io::AsRawFd::as_raw_fd(&pair.slots[wrote_idx].fd);
|
||||
result = Some((bw, bh, bw * 4, memfd_raw));
|
||||
});
|
||||
result
|
||||
});
|
||||
|
||||
if let Some((bw, bh, stride)) = shm_meta {
|
||||
if let Some((bw, bh, stride, memfd_raw)) = shm_meta {
|
||||
if let Some(ref ipc) = data.state.electron_ipc {
|
||||
if let Err(e) = ipc.broadcast_shm_buffer(
|
||||
&window_name, bw, bh, stride,
|
||||
pid, memfd_fd.as_raw_fd() as i32,
|
||||
pid, memfd_raw,
|
||||
&damage_rects,
|
||||
) {
|
||||
debug!("DRM shm buffer broadcast failed for {}: {}", window_name, e);
|
||||
}
|
||||
}
|
||||
// Flip to the other buffer for next frame
|
||||
*idx = 1 - *idx;
|
||||
} else if let Some(ref ipc) = data.state.electron_ipc {
|
||||
// Try DMABUF (hardware-rendered clients / games)
|
||||
let dmabuf_opt = with_states(surface, |states| {
|
||||
|
|
|
|||
252
src/nested.rs
252
src/nested.rs
|
|
@ -746,14 +746,27 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
|
||||
drop(target);
|
||||
|
||||
// Export dirty Wayland surfaces via offscreen rendering to SHM
|
||||
let mut exported_pixels: Vec<crate::state::ShmExport> = Vec::new();
|
||||
// Export dirty Wayland surfaces directly to mmap'd memfds (zero intermediate copy).
|
||||
// BGRA→RGBA conversion happens during the copy so the client can skip it.
|
||||
{
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::wayland::compositor::{with_states, Damage, SurfaceAttributes};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
let dirty: Vec<_> = state.dirty_surfaces.drain().collect();
|
||||
|
||||
// Collect exports into a Vec so we can send IPC after releasing surface borrows.
|
||||
struct ShmExportMeta {
|
||||
window_name: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
/// Output stride (always width*4, tight packing in RGBA).
|
||||
stride: u32,
|
||||
memfd_raw_fd: i32,
|
||||
damage_rects: Vec<crate::state::DamageRect>,
|
||||
}
|
||||
let mut shm_exports: Vec<ShmExportMeta> = Vec::new();
|
||||
|
||||
for surface in dirty {
|
||||
let surface_id = surface.id();
|
||||
let (window_name, window_geo) = if let Some(n) =
|
||||
|
|
@ -824,41 +837,132 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Read SHM buffer directly (for software-rendered clients)
|
||||
// ── Direct SHM→mmap copy with BGRA→RGBA conversion ──────────
|
||||
use smithay::backend::renderer::utils::RendererSurfaceStateUserData;
|
||||
use smithay::wayland::shm::with_buffer_contents;
|
||||
|
||||
let pixels_opt: Option<(u32, u32, u32, Vec<u8>)> =
|
||||
with_states(&surface, |states| {
|
||||
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>()?;
|
||||
let rss = rss_data.lock().ok()?;
|
||||
let buffer = rss.buffer()?;
|
||||
let wrote_ok: bool = with_states(&surface, |states| {
|
||||
let rss_data = match states.data_map.get::<RendererSurfaceStateUserData>() {
|
||||
Some(d) => d,
|
||||
None => return false,
|
||||
};
|
||||
let rss = match rss_data.lock() {
|
||||
Ok(r) => r,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let buffer = match rss.buffer() {
|
||||
Some(b) => b,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let mut result: Option<(u32, u32, u32, Vec<u8>)> = None;
|
||||
let _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
||||
let pixel_bytes =
|
||||
(spec.stride.max(0) * spec.height.max(0)) as usize;
|
||||
if pixel_bytes > 0 && pixel_bytes <= len {
|
||||
let slice =
|
||||
unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
||||
result = Some((
|
||||
spec.width as u32,
|
||||
spec.height as u32,
|
||||
spec.stride as u32,
|
||||
slice.to_vec(),
|
||||
));
|
||||
let mut ok = false;
|
||||
let _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
||||
let src_stride = spec.stride.max(0) as usize;
|
||||
let bw = spec.width.max(0) as u32;
|
||||
let bh = spec.height.max(0) as u32;
|
||||
let pixel_bytes = src_stride * bh as usize;
|
||||
if pixel_bytes == 0 || pixel_bytes > len {
|
||||
return;
|
||||
}
|
||||
|
||||
// Output stride: tightly packed RGBA (width * 4)
|
||||
let dst_stride = (bw as usize) * 4;
|
||||
let dst_size = dst_stride * bh as usize;
|
||||
|
||||
// Get or create the mmap'd slot pair for this window.
|
||||
let pair = state
|
||||
.window_shm_slots
|
||||
.entry(window_name.clone())
|
||||
.or_insert_with(|| {
|
||||
crate::state::ShmSlotPair::new(dst_size)
|
||||
.expect("creating ShmSlotPair")
|
||||
});
|
||||
|
||||
let slot = pair.write_slot_mut();
|
||||
if let Err(e) = slot.ensure_size(dst_size) {
|
||||
warn!("Failed to resize SHM slot for {}: {}", window_name, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: ping-pong guarantees Electron reads the other slot.
|
||||
let dst = unsafe { slot.as_mut_slice() };
|
||||
let src = unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
||||
|
||||
// Determine whether to do a partial (damage-rect) or full copy.
|
||||
let do_partial = !damage_rects.is_empty()
|
||||
&& damage_rects.len() < 64;
|
||||
|
||||
if do_partial {
|
||||
// Partial update: only copy damaged rows with BGRA→RGBA swap
|
||||
for rect in &damage_rects {
|
||||
let rx = (rect.x as usize).min(bw as usize);
|
||||
let ry = (rect.y as usize).min(bh as usize);
|
||||
let rw = (rect.width as usize).min(bw as usize - rx);
|
||||
let rh = (rect.height as usize).min(bh as usize - ry);
|
||||
for row in ry..ry + rh {
|
||||
let src_row_off = row * src_stride + rx * 4;
|
||||
let dst_row_off = row * dst_stride + rx * 4;
|
||||
for col in 0..rw {
|
||||
let si = src_row_off + col * 4;
|
||||
let di = dst_row_off + col * 4;
|
||||
// BGRA → RGBA: swap B↔R
|
||||
dst[di] = src[si + 2]; // R
|
||||
dst[di + 1] = src[si + 1]; // G
|
||||
dst[di + 2] = src[si]; // B
|
||||
dst[di + 3] = src[si + 3]; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
} else {
|
||||
// Full frame: copy all rows with BGRA→RGBA swap
|
||||
for row in 0..bh as usize {
|
||||
let src_row_off = row * src_stride;
|
||||
let dst_row_off = row * dst_stride;
|
||||
for col in 0..bw as usize {
|
||||
let si = src_row_off + col * 4;
|
||||
let di = dst_row_off + col * 4;
|
||||
dst[di] = src[si + 2]; // R
|
||||
dst[di + 1] = src[si + 1]; // G
|
||||
dst[di + 2] = src[si]; // B
|
||||
dst[di + 3] = src[si + 3]; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
ok = true;
|
||||
});
|
||||
ok
|
||||
});
|
||||
|
||||
if wrote_ok {
|
||||
// The slot pair was already flipped by write_slot_mut(); the
|
||||
// "previous" write slot is at index (current_write_idx) since
|
||||
// write_slot_mut already toggled it.
|
||||
let pair = state.window_shm_slots.get(&window_name).unwrap();
|
||||
// After write_slot_mut() the write_idx was flipped, so the
|
||||
// slot we just wrote to is at `1 - pair.write_idx`.
|
||||
let wrote_idx = 1 - pair.current_write_idx();
|
||||
let memfd_raw = pair.slots[wrote_idx].fd.as_raw_fd();
|
||||
|
||||
let bw_h: (u32, u32) = with_states(&surface, |states| {
|
||||
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>();
|
||||
rss_data
|
||||
.and_then(|d| d.lock().ok())
|
||||
.and_then(|rss| rss.buffer().map(|b| {
|
||||
let mut r = (0u32, 0u32);
|
||||
let _ = with_buffer_contents(&b, |_ptr, _len, spec| {
|
||||
r = (spec.width.max(0) as u32, spec.height.max(0) as u32);
|
||||
});
|
||||
r
|
||||
}))
|
||||
.unwrap_or((w, h))
|
||||
});
|
||||
|
||||
if let Some((bw, bh, stride, pixels)) = pixels_opt {
|
||||
exported_pixels.push(crate::state::ShmExport {
|
||||
shm_exports.push(ShmExportMeta {
|
||||
window_name,
|
||||
width: bw,
|
||||
height: bh,
|
||||
stride,
|
||||
pixels,
|
||||
width: bw_h.0,
|
||||
height: bw_h.1,
|
||||
stride: bw_h.0 * 4,
|
||||
memfd_raw_fd: memfd_raw,
|
||||
damage_rects,
|
||||
});
|
||||
} else if let Some(ref ipc) = state.electron_ipc {
|
||||
|
|
@ -931,10 +1035,6 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
)?;
|
||||
frame.clear(Color32F::TRANSPARENT, &[output_rect])?;
|
||||
|
||||
// Draw each element in the surface tree
|
||||
// Elements are positioned relative to surface origin (0, 0)
|
||||
// For CSD windows, we DON'T offset - surface origin should already
|
||||
// include the decoration area, and geometry() positions are correct
|
||||
for element in &elements {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(scale);
|
||||
|
|
@ -949,8 +1049,6 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Read back pixels from offscreen texture
|
||||
// Skip GL rendering for now - only export SHM buffers
|
||||
warn!(
|
||||
"Skipping GL offscreen render for {} - only SHM buffers supported",
|
||||
window_name
|
||||
|
|
@ -960,8 +1058,27 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// Store exported pixels for memfd write after render block
|
||||
state.pending_shm_exports = exported_pixels;
|
||||
// Broadcast SHM metadata to Electron (pixels already in mmap'd memfds).
|
||||
if let Some(ref ipc) = state.electron_ipc {
|
||||
let pid = std::process::id();
|
||||
for meta in &shm_exports {
|
||||
if let Err(e) = ipc.broadcast_shm_buffer(
|
||||
&meta.window_name,
|
||||
meta.width,
|
||||
meta.height,
|
||||
meta.stride,
|
||||
pid,
|
||||
meta.memfd_raw_fd,
|
||||
&meta.damage_rects,
|
||||
) {
|
||||
debug!(
|
||||
"nested shm buffer broadcast failed for {}: {}",
|
||||
meta.window_name, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.dirty_surfaces.clear();
|
||||
} // 'render block ends here, backend borrow is released
|
||||
|
||||
|
|
@ -999,66 +1116,7 @@ pub fn run_nested(config: Config) -> Result<()> {
|
|||
});
|
||||
}
|
||||
|
||||
// Write exported pixel buffers to SHM and broadcast to Electron.
|
||||
// Uses ping-pong double-buffered memfds (same as DRM path).
|
||||
if did_bind {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
let exports = std::mem::take(&mut state.pending_shm_exports);
|
||||
for export in exports {
|
||||
let pair = state
|
||||
.window_shm_buffers
|
||||
.entry(export.window_name.clone())
|
||||
.or_insert_with(|| {
|
||||
let a = nix::sys::memfd::memfd_create(
|
||||
std::ffi::CStr::from_bytes_with_nul(b"wayland_window\0").unwrap(),
|
||||
MFdFlags::MFD_CLOEXEC,
|
||||
)
|
||||
.expect("creating memfd A");
|
||||
let b = nix::sys::memfd::memfd_create(
|
||||
std::ffi::CStr::from_bytes_with_nul(b"wayland_window\0").unwrap(),
|
||||
MFdFlags::MFD_CLOEXEC,
|
||||
)
|
||||
.expect("creating memfd B");
|
||||
[a, b]
|
||||
});
|
||||
|
||||
let idx = state.window_shm_write_idx
|
||||
.entry(export.window_name.clone())
|
||||
.or_insert(0);
|
||||
let memfd_fd = &pair[*idx];
|
||||
|
||||
let size = export.pixels.len() as i64;
|
||||
let stat = nix::sys::stat::fstat(memfd_fd).unwrap();
|
||||
if stat.st_size != size {
|
||||
nix::unistd::ftruncate(memfd_fd, size).expect("truncating memfd");
|
||||
}
|
||||
|
||||
nix::unistd::lseek(memfd_fd, 0, nix::unistd::Whence::SeekSet)
|
||||
.expect("seeking memfd");
|
||||
nix::unistd::write(memfd_fd, &export.pixels).expect("writing to memfd");
|
||||
|
||||
if let Some(ref ipc) = state.electron_ipc {
|
||||
let pid = std::process::id();
|
||||
if let Err(e) = ipc.broadcast_shm_buffer(
|
||||
&export.window_name,
|
||||
export.width,
|
||||
export.height,
|
||||
export.stride,
|
||||
pid,
|
||||
memfd_fd.as_raw_fd() as i32,
|
||||
&export.damage_rects,
|
||||
) {
|
||||
debug!(
|
||||
"nested shm buffer broadcast failed for {}: {}",
|
||||
export.window_name, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*idx = 1 - *idx;
|
||||
}
|
||||
}
|
||||
// SHM exports already written directly to mmap'd memfds and broadcast above.
|
||||
|
||||
let render_total_ms = render_t_start.elapsed().as_millis();
|
||||
if render_total_ms > 50 {
|
||||
|
|
|
|||
156
src/state.rs
156
src/state.rs
|
|
@ -45,14 +45,141 @@ pub struct DamageRect {
|
|||
pub height: i32,
|
||||
}
|
||||
|
||||
/// Buffered SHM pixel export with per-surface damage information.
|
||||
pub struct ShmExport {
|
||||
pub window_name: String,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
pub pixels: Vec<u8>,
|
||||
pub damage_rects: Vec<DamageRect>,
|
||||
/// mmap-backed memfd slot for zero-copy SHM export.
|
||||
///
|
||||
/// Instead of copying pixels into a `Vec` and later `write()`-ing to a memfd,
|
||||
/// we keep the memfd persistently mmap'd so the compositor can `memcpy`
|
||||
/// directly from the Wayland SHM pool into the mapped region — eliminating
|
||||
/// two copies per frame. Electron reads the same memfd via
|
||||
/// `/proc/<pid>/fd/<fd>` without any additional data movement.
|
||||
pub struct MappedShmSlot {
|
||||
pub fd: std::os::unix::io::OwnedFd,
|
||||
ptr: *mut u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
// SAFETY: The mmap pointer is only accessed on the compositor main thread.
|
||||
unsafe impl Send for MappedShmSlot {}
|
||||
|
||||
impl MappedShmSlot {
|
||||
/// Create a new slot backed by a memfd of `size` bytes.
|
||||
pub fn new(size: usize) -> anyhow::Result<Self> {
|
||||
use anyhow::Context;
|
||||
let fd = nix::sys::memfd::memfd_create(
|
||||
std::ffi::CStr::from_bytes_with_nul(b"wayland_window\0").unwrap(),
|
||||
nix::sys::memfd::MFdFlags::MFD_CLOEXEC,
|
||||
)
|
||||
.context("creating memfd for MappedShmSlot")?;
|
||||
nix::unistd::ftruncate(&fd, size as i64).context("ftruncate memfd")?;
|
||||
|
||||
let ptr = unsafe {
|
||||
libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
std::os::unix::io::AsRawFd::as_raw_fd(&fd),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ptr == libc::MAP_FAILED {
|
||||
anyhow::bail!("mmap failed for MappedShmSlot");
|
||||
}
|
||||
|
||||
Ok(Self { fd, ptr: ptr as *mut u8, len: size })
|
||||
}
|
||||
|
||||
/// Resize the slot, remapping if the new size differs.
|
||||
pub fn ensure_size(&mut self, new_size: usize) -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
if new_size == self.len {
|
||||
return Ok(());
|
||||
}
|
||||
// munmap old
|
||||
unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
|
||||
nix::unistd::ftruncate(&self.fd, new_size as i64).context("ftruncate resize")?;
|
||||
let ptr = unsafe {
|
||||
libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
new_size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
std::os::unix::io::AsRawFd::as_raw_fd(&self.fd),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ptr == libc::MAP_FAILED {
|
||||
anyhow::bail!("mmap failed on resize");
|
||||
}
|
||||
self.ptr = ptr as *mut u8;
|
||||
self.len = new_size;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a mutable slice over the mapped region.
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller must ensure no concurrent reader is accessing the slot
|
||||
/// (the ping-pong scheme guarantees this).
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||
std::slice::from_raw_parts_mut(self.ptr, self.len)
|
||||
}
|
||||
|
||||
/// Copy `src` into the mapped region starting at byte offset `offset`.
|
||||
/// # Safety
|
||||
/// Same as `as_mut_slice`.
|
||||
#[inline]
|
||||
pub unsafe fn write_at(&mut self, offset: usize, src: &[u8]) {
|
||||
debug_assert!(offset + src.len() <= self.len);
|
||||
std::ptr::copy_nonoverlapping(src.as_ptr(), self.ptr.add(offset), src.len());
|
||||
}
|
||||
|
||||
/// Write a single pixel (4 bytes) at the given byte offset.
|
||||
#[inline]
|
||||
pub unsafe fn write_pixel(&mut self, offset: usize, pixel: [u8; 4]) {
|
||||
debug_assert!(offset + 4 <= self.len);
|
||||
std::ptr::copy_nonoverlapping(pixel.as_ptr(), self.ptr.add(offset), 4);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MappedShmSlot {
|
||||
fn drop(&mut self) {
|
||||
if self.len > 0 {
|
||||
unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ping-pong pair of mmap'd memfd slots for a window.
|
||||
pub struct ShmSlotPair {
|
||||
pub slots: [MappedShmSlot; 2],
|
||||
pub write_idx: usize,
|
||||
}
|
||||
|
||||
impl ShmSlotPair {
|
||||
pub fn new(size: usize) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
slots: [MappedShmSlot::new(size)?, MappedShmSlot::new(size)?],
|
||||
write_idx: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current write slot (mutably) and flip the index for next frame.
|
||||
pub fn write_slot_mut(&mut self) -> &mut MappedShmSlot {
|
||||
let idx = self.write_idx;
|
||||
self.write_idx = 1 - self.write_idx;
|
||||
&mut self.slots[idx]
|
||||
}
|
||||
|
||||
/// Peek the current write slot index (before flip).
|
||||
pub fn current_write_idx(&self) -> usize {
|
||||
self.write_idx
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -126,13 +253,8 @@ pub struct WoState {
|
|||
pub metadata_dirty: bool,
|
||||
/// Wayland surfaces that committed a new buffer and need offscreen capture.
|
||||
pub dirty_surfaces: std::collections::HashSet<WlSurface>,
|
||||
/// Ping-pong SHM memfd pairs per window. Index 0 is being written by
|
||||
/// the compositor while Electron reads index 1 (and vice versa).
|
||||
pub window_shm_buffers: std::collections::HashMap<String, [std::os::unix::io::OwnedFd; 2]>,
|
||||
/// Tracks which of the two memfds is the current write target (0 or 1).
|
||||
pub window_shm_write_idx: std::collections::HashMap<String, usize>,
|
||||
/// Buffered pixel exports from offscreen rendering, written to SHM after render block.
|
||||
pub pending_shm_exports: Vec<ShmExport>,
|
||||
/// Ping-pong mmap'd memfd pairs per window for zero-copy SHM export.
|
||||
pub window_shm_slots: std::collections::HashMap<String, ShmSlotPair>,
|
||||
pub grab_state: Option<crate::handlers::xdg_shell::GrabState>,
|
||||
pub cursor_theme_manager: CursorThemeManager,
|
||||
|
||||
|
|
@ -328,9 +450,7 @@ impl WoState {
|
|||
damaged_windows: std::collections::HashSet::new(),
|
||||
metadata_dirty: true,
|
||||
dirty_surfaces: std::collections::HashSet::new(),
|
||||
window_shm_buffers: std::collections::HashMap::new(),
|
||||
window_shm_write_idx: std::collections::HashMap::new(),
|
||||
pending_shm_exports: Vec::new(),
|
||||
window_shm_slots: std::collections::HashMap::new(),
|
||||
grab_state: None,
|
||||
cursor_theme_manager,
|
||||
debug_new_toplevel: false,
|
||||
|
|
|
|||
Loading…
Reference in a new issue