Further optimization passes

This commit is contained in:
Stephen Hellings 2026-03-10 10:26:14 -04:00
parent 03b73066cb
commit 104261d74c
3 changed files with 353 additions and 178 deletions

View file

@ -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| {

View file

@ -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,
slice.to_vec(),
));
} }
});
result // 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")
}); });
if let Some((bw, bh, stride, pixels)) = pixels_opt { let slot = pair.write_slot_mut();
exported_pixels.push(crate::state::ShmExport { 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 {
// 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))
});
shm_exports.push(ShmExportMeta {
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 {

View file

@ -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,