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()
|
}).collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prepare memfd pair for this window (ping-pong double buffer)
|
// Try SHM buffer: write directly to mmap'd memfd with BGRA→RGBA conversion
|
||||||
let pair = data.state.window_shm_buffers
|
let shm_meta: Option<(u32, u32, u32, i32)> = with_states(surface, |states| {
|
||||||
.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| {
|
|
||||||
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>()?;
|
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>()?;
|
||||||
let rss = rss_data.lock().ok()?;
|
let rss = rss_data.lock().ok()?;
|
||||||
let buffer = rss.buffer()?;
|
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 _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
||||||
let pixel_bytes =
|
let src_stride = spec.stride.max(0) as usize;
|
||||||
(spec.stride.max(0) * spec.height.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; }
|
if pixel_bytes == 0 || pixel_bytes > len { return; }
|
||||||
|
|
||||||
let slice =
|
let dst_stride = (bw as usize) * 4;
|
||||||
unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
let dst_size = dst_stride * bh as usize;
|
||||||
|
|
||||||
// Ensure memfd is correctly sized
|
let pair = data.state.window_shm_slots
|
||||||
let size = pixel_bytes as i64;
|
.entry(window_name.clone())
|
||||||
let cur_size = nix::sys::stat::fstat(memfd_fd)
|
.or_insert_with(|| {
|
||||||
.map(|s| s.st_size).unwrap_or(0);
|
crate::state::ShmSlotPair::new(dst_size)
|
||||||
if cur_size != size {
|
.expect("creating ShmSlotPair")
|
||||||
let _ = nix::unistd::ftruncate(memfd_fd, size);
|
});
|
||||||
|
|
||||||
|
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
|
let dst = unsafe { slot.as_mut_slice() };
|
||||||
// area and the memfd was already the correct size (meaning
|
let src = unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
||||||
// 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;
|
|
||||||
|
|
||||||
if usable_damage {
|
let do_partial = !damage_rects.is_empty()
|
||||||
let stride = spec.stride as usize;
|
&& damage_rects.len() < 64;
|
||||||
let height = spec.height as usize;
|
|
||||||
|
if do_partial {
|
||||||
for rect in &damage_rects {
|
for rect in &damage_rects {
|
||||||
let y0 = (rect.y.max(0) as usize).min(height);
|
let rx = (rect.x as usize).min(bw as usize);
|
||||||
let y1 = ((rect.y + rect.height).max(0) as usize).min(height);
|
let ry = (rect.y as usize).min(bh as usize);
|
||||||
if y0 >= y1 { continue; }
|
let rw = (rect.width as usize).min(bw as usize - rx);
|
||||||
let byte_start = y0 * stride;
|
let rh = (rect.height as usize).min(bh as usize - ry);
|
||||||
let byte_end = y1 * stride;
|
for row in ry..ry + rh {
|
||||||
let _ = nix::unistd::lseek(
|
let src_row_off = row * src_stride + rx * 4;
|
||||||
memfd_fd, byte_start as i64,
|
let dst_row_off = row * dst_stride + rx * 4;
|
||||||
nix::unistd::Whence::SeekSet,
|
for col in 0..rw {
|
||||||
);
|
let si = src_row_off + col * 4;
|
||||||
let _ = nix::unistd::write(memfd_fd, &slice[byte_start..byte_end]);
|
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 {
|
} else {
|
||||||
let _ = nix::unistd::lseek(memfd_fd, 0, nix::unistd::Whence::SeekSet);
|
for row in 0..bh as usize {
|
||||||
let _ = nix::unistd::write(memfd_fd, slice);
|
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((
|
// Get the fd of the slot we just wrote to (write_slot_mut already flipped)
|
||||||
spec.width as u32,
|
let wrote_idx = 1 - pair.current_write_idx();
|
||||||
spec.height as u32,
|
let memfd_raw = std::os::unix::io::AsRawFd::as_raw_fd(&pair.slots[wrote_idx].fd);
|
||||||
spec.stride as u32,
|
result = Some((bw, bh, bw * 4, memfd_raw));
|
||||||
));
|
|
||||||
});
|
});
|
||||||
result
|
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 Some(ref ipc) = data.state.electron_ipc {
|
||||||
if let Err(e) = ipc.broadcast_shm_buffer(
|
if let Err(e) = ipc.broadcast_shm_buffer(
|
||||||
&window_name, bw, bh, stride,
|
&window_name, bw, bh, stride,
|
||||||
pid, memfd_fd.as_raw_fd() as i32,
|
pid, memfd_raw,
|
||||||
&damage_rects,
|
&damage_rects,
|
||||||
) {
|
) {
|
||||||
debug!("DRM shm buffer broadcast failed for {}: {}", window_name, e);
|
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 {
|
} else if let Some(ref ipc) = data.state.electron_ipc {
|
||||||
// Try DMABUF (hardware-rendered clients / games)
|
// Try DMABUF (hardware-rendered clients / games)
|
||||||
let dmabuf_opt = with_states(surface, |states| {
|
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);
|
drop(target);
|
||||||
|
|
||||||
// Export dirty Wayland surfaces via offscreen rendering to SHM
|
// Export dirty Wayland surfaces directly to mmap'd memfds (zero intermediate copy).
|
||||||
let mut exported_pixels: Vec<crate::state::ShmExport> = Vec::new();
|
// BGRA→RGBA conversion happens during the copy so the client can skip it.
|
||||||
{
|
{
|
||||||
use smithay::reexports::wayland_server::Resource;
|
use smithay::reexports::wayland_server::Resource;
|
||||||
use smithay::wayland::compositor::{with_states, Damage, SurfaceAttributes};
|
use smithay::wayland::compositor::{with_states, Damage, SurfaceAttributes};
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
let dirty: Vec<_> = state.dirty_surfaces.drain().collect();
|
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 {
|
for surface in dirty {
|
||||||
let surface_id = surface.id();
|
let surface_id = surface.id();
|
||||||
let (window_name, window_geo) = if let Some(n) =
|
let (window_name, window_geo) = if let Some(n) =
|
||||||
|
|
@ -824,41 +837,132 @@ pub fn run_nested(config: Config) -> Result<()> {
|
||||||
continue;
|
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::backend::renderer::utils::RendererSurfaceStateUserData;
|
||||||
use smithay::wayland::shm::with_buffer_contents;
|
use smithay::wayland::shm::with_buffer_contents;
|
||||||
|
|
||||||
let pixels_opt: Option<(u32, u32, u32, Vec<u8>)> =
|
let wrote_ok: bool = with_states(&surface, |states| {
|
||||||
with_states(&surface, |states| {
|
let rss_data = match states.data_map.get::<RendererSurfaceStateUserData>() {
|
||||||
let rss_data = states.data_map.get::<RendererSurfaceStateUserData>()?;
|
Some(d) => d,
|
||||||
let rss = rss_data.lock().ok()?;
|
None => return false,
|
||||||
let buffer = rss.buffer()?;
|
};
|
||||||
|
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 mut ok = false;
|
||||||
let _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
let _ = with_buffer_contents(buffer, |ptr, len, spec| {
|
||||||
let pixel_bytes =
|
let src_stride = spec.stride.max(0) as usize;
|
||||||
(spec.stride.max(0) * spec.height.max(0)) as usize;
|
let bw = spec.width.max(0) as u32;
|
||||||
if pixel_bytes > 0 && pixel_bytes <= len {
|
let bh = spec.height.max(0) as u32;
|
||||||
let slice =
|
let pixel_bytes = src_stride * bh as usize;
|
||||||
unsafe { std::slice::from_raw_parts(ptr, pixel_bytes) };
|
if pixel_bytes == 0 || pixel_bytes > len {
|
||||||
result = Some((
|
return;
|
||||||
spec.width as u32,
|
}
|
||||||
spec.height as u32,
|
|
||||||
spec.stride as u32,
|
// Output stride: tightly packed RGBA (width * 4)
|
||||||
slice.to_vec(),
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
result
|
// 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 {
|
shm_exports.push(ShmExportMeta {
|
||||||
exported_pixels.push(crate::state::ShmExport {
|
|
||||||
window_name,
|
window_name,
|
||||||
width: bw,
|
width: bw_h.0,
|
||||||
height: bh,
|
height: bw_h.1,
|
||||||
stride,
|
stride: bw_h.0 * 4,
|
||||||
pixels,
|
memfd_raw_fd: memfd_raw,
|
||||||
damage_rects,
|
damage_rects,
|
||||||
});
|
});
|
||||||
} else if let Some(ref ipc) = state.electron_ipc {
|
} 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])?;
|
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 {
|
for element in &elements {
|
||||||
let src = element.src();
|
let src = element.src();
|
||||||
let dst = element.geometry(scale);
|
let dst = element.geometry(scale);
|
||||||
|
|
@ -949,8 +1049,6 @@ pub fn run_nested(config: Config) -> Result<()> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read back pixels from offscreen texture
|
|
||||||
// Skip GL rendering for now - only export SHM buffers
|
|
||||||
warn!(
|
warn!(
|
||||||
"Skipping GL offscreen render for {} - only SHM buffers supported",
|
"Skipping GL offscreen render for {} - only SHM buffers supported",
|
||||||
window_name
|
window_name
|
||||||
|
|
@ -960,8 +1058,27 @@ pub fn run_nested(config: Config) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store exported pixels for memfd write after render block
|
// Broadcast SHM metadata to Electron (pixels already in mmap'd memfds).
|
||||||
state.pending_shm_exports = exported_pixels;
|
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();
|
state.dirty_surfaces.clear();
|
||||||
} // 'render block ends here, backend borrow is released
|
} // '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.
|
// SHM exports already written directly to mmap'd memfds and broadcast above.
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_total_ms = render_t_start.elapsed().as_millis();
|
let render_total_ms = render_t_start.elapsed().as_millis();
|
||||||
if render_total_ms > 50 {
|
if render_total_ms > 50 {
|
||||||
|
|
|
||||||
156
src/state.rs
156
src/state.rs
|
|
@ -45,14 +45,141 @@ pub struct DamageRect {
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Buffered SHM pixel export with per-surface damage information.
|
/// mmap-backed memfd slot for zero-copy SHM export.
|
||||||
pub struct ShmExport {
|
///
|
||||||
pub window_name: String,
|
/// Instead of copying pixels into a `Vec` and later `write()`-ing to a memfd,
|
||||||
pub width: u32,
|
/// we keep the memfd persistently mmap'd so the compositor can `memcpy`
|
||||||
pub height: u32,
|
/// directly from the Wayland SHM pool into the mapped region — eliminating
|
||||||
pub stride: u32,
|
/// two copies per frame. Electron reads the same memfd via
|
||||||
pub pixels: Vec<u8>,
|
/// `/proc/<pid>/fd/<fd>` without any additional data movement.
|
||||||
pub damage_rects: Vec<DamageRect>,
|
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)]
|
#[derive(Default)]
|
||||||
|
|
@ -126,13 +253,8 @@ pub struct WoState {
|
||||||
pub metadata_dirty: bool,
|
pub metadata_dirty: bool,
|
||||||
/// Wayland surfaces that committed a new buffer and need offscreen capture.
|
/// Wayland surfaces that committed a new buffer and need offscreen capture.
|
||||||
pub dirty_surfaces: std::collections::HashSet<WlSurface>,
|
pub dirty_surfaces: std::collections::HashSet<WlSurface>,
|
||||||
/// Ping-pong SHM memfd pairs per window. Index 0 is being written by
|
/// Ping-pong mmap'd memfd pairs per window for zero-copy SHM export.
|
||||||
/// the compositor while Electron reads index 1 (and vice versa).
|
pub window_shm_slots: std::collections::HashMap<String, ShmSlotPair>,
|
||||||
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>,
|
|
||||||
pub grab_state: Option<crate::handlers::xdg_shell::GrabState>,
|
pub grab_state: Option<crate::handlers::xdg_shell::GrabState>,
|
||||||
pub cursor_theme_manager: CursorThemeManager,
|
pub cursor_theme_manager: CursorThemeManager,
|
||||||
|
|
||||||
|
|
@ -328,9 +450,7 @@ impl WoState {
|
||||||
damaged_windows: std::collections::HashSet::new(),
|
damaged_windows: std::collections::HashSet::new(),
|
||||||
metadata_dirty: true,
|
metadata_dirty: true,
|
||||||
dirty_surfaces: std::collections::HashSet::new(),
|
dirty_surfaces: std::collections::HashSet::new(),
|
||||||
window_shm_buffers: std::collections::HashMap::new(),
|
window_shm_slots: std::collections::HashMap::new(),
|
||||||
window_shm_write_idx: std::collections::HashMap::new(),
|
|
||||||
pending_shm_exports: Vec::new(),
|
|
||||||
grab_state: None,
|
grab_state: None,
|
||||||
cursor_theme_manager,
|
cursor_theme_manager,
|
||||||
debug_new_toplevel: false,
|
debug_new_toplevel: false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue