diff options
| author | srdusr <[email protected]> | 2024-12-24 20:44:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-12-24 20:44:00 +0200 |
| commit | 3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f (patch) | |
| tree | 71e9bab462b9073444183e2a969ba512cb94559f /crates/wayland/src/screencopy.rs | |
| parent | 331a1a1d3a0d8dfd4c285b755c2e13785c95ee2a (diff) | |
| download | srdwm-3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f.tar.gz srdwm-3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f.zip | |
Accumulate wayland-crate additions: capture rendering, focus/Space sync, VT resume, plumbing
Bundles the remaining wayland-crate changes built up here,
touching both backends (udev and winit) and the shared input/rendering
code:
- udev/capture.rs: off-screen Pixman render of an arbitrary (not
necessarily on-screen) workspace's window content to a PPM file --
what crates/core's capture-request queue drives, for a workspace
switcher's thumbnail previews. wlr-screencopy structurally can't do
this (it can only see what an output is presenting), which is why
this exists as a separate render path rather than reusing it.
- input::focus_window now also raises the window in smithay's own
Space, not just core's stacking order - Space is what actually
renders on top and what pointer hit-testing reads, so any focus path
that skipped this (an IPC "focus" dispatch, concretely) left a
window genuinely focused while still rendering, and receiving
clicks, underneath whatever was already topmost. Both backends'
poll loops now re-sync this after any IPC mutation.
- udev/session.rs's VT-switch resume fix (drains a stale pending page
flip before reasserting CRTCs) already has its own earlier, cleanly
isolated commit - not duplicated here.
- Assorted decoration/cursor/rounded-corners/output-management/
screencopy/XWayland changes and their cross-backend wiring.
Coarser than the repo's usual one-purpose-per-commit convention,
deliberately - see the core-crate sweep commit's own message for why.
Diffstat (limited to 'crates/wayland/src/screencopy.rs')
| -rw-r--r-- | crates/wayland/src/screencopy.rs | 96 |
1 files changed, 83 insertions, 13 deletions
diff --git a/crates/wayland/src/screencopy.rs b/crates/wayland/src/screencopy.rs index cb632aa..14627e2 100644 --- a/crates/wayland/src/screencopy.rs +++ b/crates/wayland/src/screencopy.rs @@ -19,7 +19,8 @@ use std::time::UNIX_EPOCH; -use smithay::backend::allocator::Fourcc; +use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufMappingMode, DmabufSyncFlags}; +use smithay::backend::allocator::{Buffer as AllocatorBuffer, Fourcc, Modifier}; use smithay::backend::renderer::{ExportMem, Renderer}; use smithay::output::Output; use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; @@ -29,6 +30,7 @@ use smithay::reexports::wayland_server::{ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }; use smithay::utils::{Buffer as BufferCoord, Physical, Rectangle, Size}; +use smithay::wayland::dmabuf::get_dmabuf; use smithay::wayland::shm::with_buffer_contents_mut; use wayland_protocols_wlr::screencopy::v1::server::{ zwlr_screencopy_frame_v1::{self, ZwlrScreencopyFrameV1}, @@ -42,6 +44,14 @@ use crate::state::CompState; const BYTES_PER_PIXEL: u32 = 4; const CAPTURE_FOURCC: Fourcc = Fourcc::Xrgb8888; const CAPTURE_SHM_FORMAT: wl_shm::Format = wl_shm::Format::Xrgb8888; +/// `PixmanRenderer::dmabuf_formats()` (`smithay-0.7.0/src/backend/renderer/ +/// pixman/mod.rs`) pairs every format it supports - `Xrgb8888` included -- +/// with `Modifier::Linear` only; it never advertises a tiled/compressed +/// modifier. So any dmabuf a client builds against srdwm's own +/// `zwp_linux_dmabuf_v1` global is guaranteed to be a plain linear buffer, +/// safe to `mmap` and `memcpy` into directly, with no vendor tiling to +/// account for. +const CAPTURE_MODIFIER: Modifier = Modifier::Linear; /// The screencopy manager global. Held by `CompState` purely to keep the /// global alive for the compositor's lifetime. @@ -55,10 +65,20 @@ impl ScreencopyState { where D: GlobalDispatch<ZwlrScreencopyManagerV1, ()> + 'static, { - // Version 3 advertises `linux_dmabuf`/`buffer_done`, which this - // software-readback implementation does not support, so cap at 2 -- - // that still covers `copy_with_damage`, which `wf-recorder` uses. - Self { _global: dh.create_global::<D, ZwlrScreencopyManagerV1, _>(2, ()) } + // Version 3 additionally advertises `linux_dmabuf`/`buffer_done`, + // letting a client request a dmabuf-backed capture instead of shm + // (what screen-sharing consumers - e.g. a WebRTC/PipeWire producer + // sitting behind xdg-desktop-portal - actually want, since it lets + // them hand the frame to a GPU pipeline without an extra copy back + // out of shared memory). This compositor has no GPU rendering path + // at all (`PixmanRenderer` is pure software, see this crate's udev + // backend), but that turns out not to matter here: the CLIENT + // allocates the dmabuf, informed by the format/modifier list this + // compositor's own `zwp_linux_dmabuf_v1` global already advertises + // (see `CAPTURE_MODIFIER`'s doc comment) - this side only ever + // needs to `mmap` the client's buffer and `memcpy` captured pixels + // into it, exactly as it already does for shm (see `copy_region`). + Self { _global: dh.create_global::<D, ZwlrScreencopyManagerV1, _>(3, ()) } } } @@ -147,6 +167,16 @@ impl Dispatch<ZwlrScreencopyManagerV1, ()> for CompState { region.size.h as u32, region.size.w as u32 * BYTES_PER_PIXEL, ); + // `linux_dmabuf`/`buffer_done` are `since = 3`; a v1/v2 client's + // `frame` object - which inherits the version the client bound the + // manager global at, not this global's advertised maximum - can't + // receive them, so this is gated the same way `damage`/`with_damage` + // already is below (client requests that don't exist below a given + // version can't reach a handler that assumes they do). + if frame.version() >= 3 { + frame.linux_dmabuf(CAPTURE_FOURCC as u32, region.size.w as u32, region.size.h as u32); + frame.buffer_done(); + } } } @@ -178,15 +208,29 @@ impl Dispatch<ZwlrScreencopyFrameV1, FrameData> for CompState { // Validate the buffer really can hold the frame before promising a // capture - a mismatch here would otherwise be a silent short write. + // `get_dmabuf` only succeeds for a buffer imported through + // `zwp_linux_dmabuf_v1` (i.e. one the client built in response to + // this frame's own `linux_dmabuf` event, since=3); anything else -- + // every v1/v2 client, and any v3 client that chose the shm offer + // instead - falls through to the pre-existing shm check below. let expected_stride = data.region.size.w as u32 * BYTES_PER_PIXEL; - let ok = with_buffer_contents_mut(&buffer, |_ptr, len, spec| { - spec.format == CAPTURE_SHM_FORMAT - && spec.width == data.region.size.w - && spec.height == data.region.size.h - && spec.stride as u32 == expected_stride - && len >= (expected_stride * data.region.size.h as u32) as usize - }) - .unwrap_or(false); + let ok = if let Ok(dmabuf) = get_dmabuf(&buffer) { + dmabuf.num_planes() == 1 + && dmabuf.format().code == CAPTURE_FOURCC + && dmabuf.format().modifier == CAPTURE_MODIFIER + && dmabuf.size().w == data.region.size.w + && dmabuf.size().h == data.region.size.h + && dmabuf.strides().next() == Some(expected_stride) + } else { + with_buffer_contents_mut(&buffer, |_ptr, len, spec| { + spec.format == CAPTURE_SHM_FORMAT + && spec.width == data.region.size.w + && spec.height == data.region.size.h + && spec.stride as u32 == expected_stride + && len >= (expected_stride * data.region.size.h as u32) as usize + }) + .unwrap_or(false) + }; if !ok { frame.post_error( zwlr_screencopy_frame_v1::Error::InvalidBuffer, @@ -309,6 +353,10 @@ where return Err(format!("readback produced {} bytes, need {}", pixels.len(), needed)); } + if let Ok(dmabuf) = get_dmabuf(buffer) { + return write_dmabuf(dmabuf, pixels, needed); + } + with_buffer_contents_mut(buffer, |ptr, len, _spec| { if len < needed { return Err(format!("client buffer holds {len} bytes, need {needed}")); @@ -323,3 +371,25 @@ where }) .map_err(|e| format!("client buffer not accessible: {e}"))? } + +/// Writes captured pixels into a client-allocated dmabuf, in place of +/// `with_buffer_contents_mut`'s shm path. The frame's `Copy`/`CopyWithDamage` +/// handler already checked `num_planes() == 1` and `modifier == +/// CAPTURE_MODIFIER` (`Modifier::Linear`) before queuing this capture, so a +/// plain single `mmap` + `memcpy` is correct here - no plane math, no tiling +/// to undo. +fn write_dmabuf(dmabuf: &Dmabuf, pixels: &[u8], needed: usize) -> Result<(), String> { + let mapping = dmabuf.map_plane(0, DmabufMappingMode::WRITE).map_err(|e| format!("map_plane: {e}"))?; + if mapping.length() < needed { + return Err(format!("client dmabuf holds {} bytes, need {needed}", mapping.length())); + } + dmabuf.sync_plane(0, DmabufSyncFlags::START | DmabufSyncFlags::WRITE).map_err(|e| format!("sync_plane(start): {e}"))?; + // SAFETY: `map_plane` guarantees `ptr()` is valid for `length()` bytes + // for as long as `mapping` is alive, and `needed <= length()` was just + // checked. Source and destination are distinct mappings. + unsafe { + std::ptr::copy_nonoverlapping(pixels.as_ptr(), mapping.ptr().cast::<u8>(), needed); + } + dmabuf.sync_plane(0, DmabufSyncFlags::END | DmabufSyncFlags::WRITE).map_err(|e| format!("sync_plane(end): {e}"))?; + Ok(()) +} |