Skip to main content

pixelflux/x11/
framebuffer.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! The screen read where the X server keeps it: SELKIES-FRAMEBUFFER, which the Selkies images'
8//! Xvfb offers for a screen held in system memory, in place of XShm's per-frame copy and
9//! XDamage's round trips.
10//!
11//! The server keeps the screen in a memfd it hands over read-only, and hands each watch a
12//! memfd of its own and an eventfd: the watch is a bitmap of the `TILE` x `TILE` tiles drawn
13//! into since the client last cleared them, which the server sets as it draws, and `armed`,
14//! which the client sets before it waits on the eventfd and the server clears as it signals
15//! once a drawing operation ends. A frame is then the tiles drawn since the surface it lands in
16//! last held the screen: the capture's pool rotates its surfaces, so each keeps the tiles it
17//! is missing ([`FramebufferGrab`]), and the cursor and the watermark composited onto one count
18//! among them. The server sets a tile for every drawing that reaches the screen, so the tiles
19//! drawn since the last frame, with the overlays that moved or changed their image, are the
20//! frame's whole change, which the pipeline takes in place of a content hash.
21//!
22//! The framebuffer's head counts drawing operations, odd while one runs, so a read is whole
23//! where the count stood even before it and unchanged after it; one an operation ran across
24//! reads again what that operation drew, and a frame that cannot get a whole read in
25//! `READ_ATTEMPTS` is grabbed through XShm, which the server answers between operations. A
26//! resize moves the screen to a framebuffer of its new size and retires the old one, which
27//! says so in its head and signals the watch; the capture then watches again.
28//!
29//! A session whose encoder takes 8-bit 4:2:0 planes (`X11Pipeline::takes_host_planes`, x264)
30//! skips the copy: each pool surface's planes, NV12 laid out as x264 encodes it in place
31//! (`Nv12Planes`), are brought up to the screen by converting the
32//! rows of tiles they miss straight from the framebuffer, under the same drawing count
33//! ([`FramebufferGrab::convert`]), so the pixels are read once, by the conversion, where a copy
34//! read and wrote them first: a full-screen change at 2560x1600 took 0.85 ms to convert in place
35//! against 1.4 ms to copy and 0.67 ms more to convert. The rows are converted across the region's
36//! whole width, as the planes are row-major. The tiles a surface misses are the same set either
37//! way, so a capture switching between its pixels and its planes (an overlay drawn onto the
38//! pixels comes or goes) starts both over from the whole screen.
39//!
40//! The path is taken only where the server answers the request, the framebuffer is sealed
41//! against shrinking (a mapping a shrink cut short would fault the read), its head agrees with
42//! the reply, and its pixels match what `GetImage` returns: that the extension is there is not
43//! proof the memory holds the screen.
44
45use std::io::IoSlice;
46use std::os::fd::{AsRawFd, OwnedFd};
47use std::sync::atomic::{AtomicU32, AtomicU64, Ordering, fence};
48use std::time::{Duration, Instant};
49
50use x11rb::connection::{Connection, RequestConnection};
51use x11rb::errors::{ParseError, ReplyError};
52use x11rb::protocol::ErrorKind;
53use x11rb::rust_connection::RustConnection;
54use x11rb::x11_utils::TryParseFd;
55
56use crate::encoders::software::Nv12Planes;
57
58const EXTENSION: &str = "SELKIES-FRAMEBUFFER";
59const MAGIC: u32 = 0x4246_4b53;
60const VERSION: u32 = 1;
61
62/// Byte offsets of the framebuffer head's fields the client reads.
63const HEAD_WIDTH: usize = 8;
64const HEAD_HEIGHT: usize = 12;
65const HEAD_STRIDE: usize = 16;
66const HEAD_BPP: usize = 20;
67const HEAD_RETIRED: usize = 28;
68const HEAD_SEQ: usize = 32;
69
70/// Byte offsets of the watch's `tile`, `tiles_x`, `tiles_y`, and `armed`, and of its bitmap.
71const WATCH_TILE: usize = 8;
72const WATCH_TILES_X: usize = 12;
73const WATCH_TILES_Y: usize = 16;
74const WATCH_ARMED: usize = 20;
75const WATCH_BITS: usize = 24;
76
77/// Reads a frame tries for a whole one before it is grabbed through XShm instead.
78const READ_ATTEMPTS: u32 = 4;
79
80/// Bytes of tiles past which a read copies its rows of tiles on `COPY_THREADS` threads
81/// (`SharedFramebuffer::copy_tiles`); below it, starting them costs more than it saves. Scoped
82/// threads of the copy's own rather than the rayon pool: waking the pool's every worker for a
83/// copy a frame kept them spinning, 1.9 cores more at 2560x1600 and 60 fps.
84const PARALLEL_BYTES: usize = 1 << 20;
85
86/// Threads a large copy runs on, the capture thread among them: a copy is bound by memory
87/// bandwidth, which a few threads fill.
88const COPY_THREADS: usize = 4;
89
90/// How long a read waits for a drawing operation under way to end before it gives up on the
91/// attempt: an operation is one request's drawing, well under a millisecond.
92const DRAW_WAIT: Duration = Duration::from_millis(2);
93
94/// A shared mapping, unmapped on drop.
95struct Mapping {
96    ptr: *mut u8,
97    len: usize,
98}
99
100impl Mapping {
101    fn new(fd: &OwnedFd, len: usize, writable: bool) -> Result<Self, String> {
102        let prot = libc::PROT_READ | if writable { libc::PROT_WRITE } else { 0 };
103        // SAFETY: a fresh shared mapping of an fd the server handed over, checked below; nothing
104        // else in this process holds or aliases it.
105        let ptr = unsafe {
106            libc::mmap(
107                std::ptr::null_mut(),
108                len,
109                prot,
110                libc::MAP_SHARED,
111                fd.as_raw_fd(),
112                0,
113            )
114        };
115        if ptr == libc::MAP_FAILED {
116            return Err(format!("mmap: {}", std::io::Error::last_os_error()));
117        }
118        Ok(Self {
119            ptr: ptr.cast(),
120            len,
121        })
122    }
123
124    /// The 32-bit word at byte `offset`, which the server writes concurrently.
125    fn word(&self, offset: usize) -> &AtomicU32 {
126        assert!(offset.is_multiple_of(4) && offset + 4 <= self.len);
127        // SAFETY: in bounds and aligned (the mapping is page-aligned), and every access to the
128        // shared word goes through the atomic.
129        unsafe { AtomicU32::from_ptr(self.ptr.add(offset).cast()) }
130    }
131
132    /// The 64-bit word at byte `offset`, which the server writes concurrently.
133    fn word64(&self, offset: usize) -> &AtomicU64 {
134        assert!(offset.is_multiple_of(8) && offset + 8 <= self.len);
135        // SAFETY: as `word`.
136        unsafe { AtomicU64::from_ptr(self.ptr.add(offset).cast()) }
137    }
138}
139
140impl Drop for Mapping {
141    fn drop(&mut self) {
142        // SAFETY: the mapping `new` made, unmapped once.
143        unsafe {
144            libc::munmap(self.ptr.cast(), self.len);
145        }
146    }
147}
148
149/// The reply to Watch: the three fds and the framebuffer's geometry.
150struct WatchReply {
151    framebuffer: OwnedFd,
152    watch: OwnedFd,
153    event: OwnedFd,
154    width: u32,
155    height: u32,
156    stride: u32,
157    bpp: u32,
158    pixel_offset: u32,
159    tile: u32,
160}
161
162impl TryParseFd for WatchReply {
163    fn try_parse_fd<'a>(
164        value: &'a [u8],
165        fds: &mut Vec<OwnedFd>,
166    ) -> Result<(Self, &'a [u8]), ParseError> {
167        if value.len() < 32 {
168            return Err(ParseError::InsufficientData);
169        }
170        if value[1] != 3 || fds.len() < 3 {
171            return Err(ParseError::MissingFileDescriptors);
172        }
173        let field = |i: usize| u32::from_ne_bytes(value[8 + 4 * i..12 + 4 * i].try_into().unwrap());
174        let mut taken = fds.drain(..3);
175        let (framebuffer, watch, event) = (
176            taken.next().unwrap(),
177            taken.next().unwrap(),
178            taken.next().unwrap(),
179        );
180        drop(taken);
181        Ok((
182            Self {
183                framebuffer,
184                watch,
185                event,
186                width: field(0),
187                height: field(1),
188                stride: field(2),
189                bpp: field(3),
190                pixel_offset: field(4),
191                tile: field(5),
192            },
193            &value[32..],
194        ))
195    }
196}
197
198/// A watch on the framebuffer of the screen of one root window (module docblock).
199pub(crate) struct SharedFramebuffer {
200    opcode: u8,
201    id: u32,
202    framebuffer: Mapping,
203    watch: Mapping,
204    event: OwnedFd,
205    pub width: u32,
206    pub height: u32,
207    stride: usize,
208    pixel_offset: usize,
209    tile: u32,
210    tiles_x: u32,
211    tiles_y: u32,
212}
213
214/// What [`SharedFramebuffer::open`] found: a watch, or why there is none.
215pub(crate) enum Opened {
216    Watch(SharedFramebuffer),
217    /// The server has no SELKIES-FRAMEBUFFER.
218    Absent,
219    /// The server has it and the watch is not usable, and why.
220    Declined(String),
221}
222
223impl SharedFramebuffer {
224    /// Watch the framebuffer of `root`'s screen, where the server offers one of 32 bits a pixel.
225    pub(crate) fn open(conn: &RustConnection, root: u32) -> Opened {
226        let opcode = match conn.extension_information(EXTENSION) {
227            Ok(Some(info)) => info.major_opcode,
228            Ok(None) => return Opened::Absent,
229            Err(e) => return Opened::Declined(format!("{EXTENSION} query: {e}")),
230        };
231        match Self::watch(conn, opcode, root) {
232            Ok(fb) => Opened::Watch(fb),
233            Err(e) => Opened::Declined(e),
234        }
235    }
236
237    fn watch(conn: &RustConnection, opcode: u8, root: u32) -> Result<Self, String> {
238        let id = conn.generate_id().map_err(|e| format!("watch id: {e}"))?;
239        let mut req = vec![opcode, 1];
240        req.extend_from_slice(&3u16.to_ne_bytes());
241        req.extend_from_slice(&id.to_ne_bytes());
242        req.extend_from_slice(&root.to_ne_bytes());
243        let reply = conn
244            .send_request_with_reply_with_fds::<WatchReply>(&[IoSlice::new(&req)], Vec::new())
245            .map_err(|e| format!("{EXTENSION} watch: {e}"))?
246            .reply()
247            .map_err(|e| match e {
248                ReplyError::X11Error(x) if x.error_kind == ErrorKind::Match => {
249                    "the screen is on the GPU".to_string()
250                }
251                e => format!("{EXTENSION} watch refused: {e}"),
252            })?;
253        if reply.bpp != 32
254            || reply.tile == 0
255            || u64::from(reply.stride) < u64::from(reply.width) * 4
256        {
257            return Err(format!(
258                "{}x{} at {} bits a pixel, stride {}, tile {}",
259                reply.width, reply.height, reply.bpp, reply.stride, reply.tile
260            ));
261        }
262        // SAFETY: a query of the seals on an fd this function owns.
263        let seals = unsafe { libc::fcntl(reply.framebuffer.as_raw_fd(), libc::F_GET_SEALS) };
264        if seals < 0 || seals & libc::F_SEAL_SHRINK == 0 {
265            return Err("the framebuffer is not sealed against shrinking".into());
266        }
267        let pixel_offset = reply.pixel_offset as usize;
268        let framebuffer = Mapping::new(
269            &reply.framebuffer,
270            pixel_offset + reply.stride as usize * reply.height as usize,
271            false,
272        )?;
273        let tiles_x = reply.width.div_ceil(reply.tile);
274        let tiles_y = reply.height.div_ceil(reply.tile);
275        let words = (tiles_x as usize * tiles_y as usize).div_ceil(64);
276        let watch = Mapping::new(&reply.watch, WATCH_BITS + words * 8, true)?;
277        let fb = Self {
278            opcode,
279            id,
280            framebuffer,
281            watch,
282            event: reply.event,
283            width: reply.width,
284            height: reply.height,
285            stride: reply.stride as usize,
286            pixel_offset,
287            tile: reply.tile,
288            tiles_x,
289            tiles_y,
290        };
291        let head_ok = fb.framebuffer.word(0).load(Ordering::Relaxed) == MAGIC
292            && fb.framebuffer.word(4).load(Ordering::Relaxed) == VERSION
293            && fb.framebuffer.word(HEAD_WIDTH).load(Ordering::Relaxed) == reply.width
294            && fb.framebuffer.word(HEAD_HEIGHT).load(Ordering::Relaxed) == reply.height
295            && fb.framebuffer.word(HEAD_STRIDE).load(Ordering::Relaxed) == reply.stride
296            && fb.framebuffer.word(HEAD_BPP).load(Ordering::Relaxed) == 32;
297        let watch_ok = fb.watch.word(0).load(Ordering::Relaxed) == MAGIC
298            && fb.watch.word(WATCH_TILE).load(Ordering::Relaxed) == reply.tile
299            && fb.watch.word(WATCH_TILES_X).load(Ordering::Relaxed) == tiles_x
300            && fb.watch.word(WATCH_TILES_Y).load(Ordering::Relaxed) == tiles_y;
301        if !head_ok || !watch_ok {
302            fb.close(conn);
303            return Err("the framebuffer's head disagrees with the reply".into());
304        }
305        Ok(fb)
306    }
307
308    /// End the watch on the server; the mappings go with `self`.
309    pub(crate) fn close(self, conn: &RustConnection) {
310        let mut req = vec![self.opcode, 2];
311        req.extend_from_slice(&2u16.to_ne_bytes());
312        req.extend_from_slice(&self.id.to_ne_bytes());
313        let _ = conn.send_request_without_reply(&[IoSlice::new(&req)], Vec::new());
314    }
315
316    /// Whether the screen has moved to another framebuffer (a resize): the watch is over.
317    pub(crate) fn retired(&self) -> bool {
318        self.framebuffer.word(HEAD_RETIRED).load(Ordering::Acquire) != 0
319    }
320
321    /// The words of the tile bitmap.
322    fn words(&self) -> usize {
323        (self.tiles_x as usize * self.tiles_y as usize).div_ceil(64)
324    }
325
326    /// Whether any tile was drawn into since the bitmap was last taken.
327    pub(crate) fn drawn(&self) -> bool {
328        (0..self.words()).any(|i| {
329            self.watch
330                .word64(WATCH_BITS + i * 8)
331                .load(Ordering::Acquire)
332                != 0
333        })
334    }
335
336    /// Take the tiles drawn since the last take, handing each nonzero word of the bitmap to
337    /// `into` with its index.
338    fn take(&self, mut into: impl FnMut(usize, u64)) {
339        for i in 0..self.words() {
340            let bits = self
341                .watch
342                .word64(WATCH_BITS + i * 8)
343                .swap(0, Ordering::AcqRel);
344            if bits != 0 {
345                into(i, bits);
346            }
347        }
348    }
349
350    /// Wait at most `timeout` for a drawing operation to end on a tile not yet taken, or for the
351    /// framebuffer to retire: true where one did, or already had.
352    pub(crate) fn wait(&self, timeout: Duration) -> bool {
353        self.watch.word(WATCH_ARMED).store(1, Ordering::SeqCst);
354        fence(Ordering::SeqCst);
355        if self.drawn() || self.retired() {
356            return true;
357        }
358        let mut poll = libc::pollfd {
359            fd: self.event.as_raw_fd(),
360            events: libc::POLLIN,
361            revents: 0,
362        };
363        let deadline = libc::timespec {
364            tv_sec: timeout.as_secs() as _,
365            tv_nsec: libc::c_long::from(timeout.subsec_nanos()),
366        };
367        // SAFETY: one pollfd for the duration of the call.
368        let ready = unsafe { libc::ppoll(&mut poll, 1, &deadline, std::ptr::null()) } > 0;
369        if ready {
370            let mut count = 0u64;
371            // SAFETY: an 8-byte read of the eventfd's counter into a u64; the fd is nonblocking.
372            unsafe {
373                libc::read(self.event.as_raw_fd(), (&mut count as *mut u64).cast(), 8);
374            }
375        }
376        ready || self.drawn() || self.retired()
377    }
378
379    /// The drawing count once no operation runs, or `None` past `DRAW_WAIT`.
380    fn settled_seq(&self) -> Option<u64> {
381        let seq = self.framebuffer.word64(HEAD_SEQ);
382        let start = Instant::now();
383        loop {
384            let s = seq.load(Ordering::Acquire);
385            if s.is_multiple_of(2) {
386                return Some(s);
387            }
388            if start.elapsed() > DRAW_WAIT {
389                return None;
390            }
391            std::hint::spin_loop();
392        }
393    }
394
395    /// Copy the tiles `pending` marks within the region at (`x`, `y`) of `width` x `height`
396    /// into `dst` (`dst_stride` bytes a row, the region's top-left at its start). Each row of
397    /// tiles lands in rows of `dst` of its own, so a change past `PARALLEL_BYTES` copies its
398    /// rows of tiles on `COPY_THREADS` threads: one thread copying a whole 2560x1600 screen took
399    /// 1.2 to 6.7 ms, reading what the server had just drawn from another core's cache, which
400    /// held a full-screen change back behind an XShm grab, the server's copy from its own.
401    fn copy_tiles(
402        &self,
403        pending: &[u64],
404        dst: &mut [u8],
405        dst_stride: usize,
406        (x, y, width, height): (u32, u32, u32, u32),
407    ) {
408        let tile = self.tile;
409        let tx0 = x / tile;
410        let tx1 = (x + width).div_ceil(tile).min(self.tiles_x);
411        let (ty0, ty1) = (y / tile, (y + height).div_ceil(tile).min(self.tiles_y));
412        let (tiles_x, fb_width, stride) = (self.tiles_x as usize, self.width, self.stride);
413        let set = |tx: u32, ty: u32| {
414            let t = ty as usize * tiles_x + tx as usize;
415            pending[t / 64] >> (t % 64) & 1 != 0
416        };
417        // SAFETY: the pixels are `stride * height` bytes past `pixel_offset` of a mapping that
418        // long (`watch`); the server writes them concurrently, which the drawing count guards.
419        let src = unsafe {
420            std::slice::from_raw_parts(
421                self.framebuffer.ptr.add(self.pixel_offset),
422                self.stride * self.height as usize,
423            )
424        };
425        let bottom_of = |ty: u32| ((ty + 1) * tile).min(y + height).min(self.height);
426        let mut rows = Vec::with_capacity((ty1 - ty0) as usize);
427        let mut rest = dst;
428        let mut copied = 0usize;
429        for ty in ty0..ty1 {
430            let (top, bottom) = ((ty * tile).max(y), bottom_of(ty));
431            let len = ((bottom - top) as usize * dst_stride).min(rest.len());
432            let (band, tail) = std::mem::take(&mut rest).split_at_mut(len);
433            rest = tail;
434            let marked = (tx0..tx1).filter(|&tx| set(tx, ty)).count();
435            copied += marked * (tile * (bottom - top)) as usize * 4;
436            if marked > 0 {
437                rows.push((ty, top, bottom, band));
438            }
439        }
440        let copy_row = |(ty, top, bottom, band): (u32, u32, u32, &mut [u8])| {
441            let mut tx = tx0;
442            while tx < tx1 {
443                if !set(tx, ty) {
444                    tx += 1;
445                    continue;
446                }
447                let run = tx;
448                while tx < tx1 && set(tx, ty) {
449                    tx += 1;
450                }
451                let left = (run * tile).max(x);
452                let right = (tx * tile).min(x + width).min(fb_width);
453                let bytes = (right - left) as usize * 4;
454                for row in top..bottom {
455                    let s = row as usize * stride + left as usize * 4;
456                    let d = (row - top) as usize * dst_stride + (left - x) as usize * 4;
457                    band[d..d + bytes].copy_from_slice(&src[s..s + bytes]);
458                }
459            }
460        };
461        if copied > PARALLEL_BYTES {
462            let per = rows.len().div_ceil(COPY_THREADS);
463            let mut parts: Vec<Vec<_>> = Vec::with_capacity(COPY_THREADS);
464            let mut rows = rows.into_iter().peekable();
465            while rows.peek().is_some() {
466                parts.push(rows.by_ref().take(per).collect());
467            }
468            let first = parts.remove(0);
469            std::thread::scope(|scope| {
470                for part in parts {
471                    scope.spawn(|| part.into_iter().for_each(copy_row));
472                }
473                first.into_iter().for_each(copy_row);
474            });
475        } else {
476            rows.into_iter().for_each(copy_row);
477        }
478    }
479
480    /// Convert the rows of tiles `pending` marks within the region at (`x`, `y`) of `width` x
481    /// `height` to 8-bit 4:2:0 BT.709 limited range in `planes` (the region's top-left at their
482    /// first pixel), as the NV12 x264 encodes in place, across the region's whole width for each
483    /// run of such rows, split into `bands` of rows converted in parallel as the encode thread's
484    /// own conversion is: the conversion reads the screen where the server keeps it, in place of
485    /// copying it out first. False where the conversion refused the geometry.
486    fn convert_tiles(
487        &self,
488        pending: &[u64],
489        planes: &mut Nv12Planes,
490        (x, y, width, height): (u32, u32, u32, u32),
491        bands: usize,
492    ) -> bool {
493        let tile = self.tile;
494        let (tx0, tx1) = (x / tile, (x + width).div_ceil(tile).min(self.tiles_x));
495        let (ty0, ty1) = (y / tile, (y + height).div_ceil(tile).min(self.tiles_y));
496        let tiles_x = self.tiles_x as usize;
497        let row_marked = |ty: u32| {
498            (tx0..tx1).any(|tx| {
499                let t = ty as usize * tiles_x + tx as usize;
500                pending[t / 64] >> (t % 64) & 1 != 0
501            })
502        };
503        // SAFETY: as `copy_tiles`: the pixels are `stride * height` bytes past `pixel_offset`
504        // of the mapping, written concurrently by the server, which the drawing count guards.
505        let src = unsafe {
506            std::slice::from_raw_parts(
507                self.framebuffer.ptr.add(self.pixel_offset),
508                self.stride * self.height as usize,
509            )
510        };
511        let mut ty = ty0;
512        while ty < ty1 {
513            if !row_marked(ty) {
514                ty += 1;
515                continue;
516            }
517            let first = ty;
518            while ty < ty1 && row_marked(ty) {
519                ty += 1;
520            }
521            let top = (first * tile).max(y);
522            let bottom = (ty * tile).min(y + height);
523            let rows = (bottom - top) as usize;
524            let start = top as usize * self.stride + x as usize * 4;
525            let (ys, uvs) = (planes.stride[0], planes.stride[1]);
526            let off = (top - y) as usize;
527            let (luma, chroma) = planes.planes_mut();
528            let converted = crate::encoders::software::convert_to_nv12_mt(
529                &src[start..],
530                self.stride as u32,
531                width as usize,
532                rows,
533                false,
534                &mut luma[off * ys..],
535                &mut chroma[off / 2 * uvs..],
536                (ys, uvs),
537                bands,
538            );
539            if converted.is_err() {
540                return false;
541            }
542        }
543        true
544    }
545}
546
547/// The capture's watch, the tiles each pool surface is missing (module docblock), and those
548/// drawn since the last frame said where it changed (`changed`).
549///
550/// A frame's change is the tiles its read took from the watch: a read takes them before it
551/// copies, and again before each copy it repeats, so every tile it took is in the surface. A
552/// tile the server draws after the read is left in the watch for the frame that will hold it,
553/// since a change named with a frame that does not hold it is never named with the one that
554/// does, and an encoder told the tile held still then keeps the stale picture until the tile
555/// is drawn again. A surface grabbed through XShm instead ([`Self::grabbed`]) takes the watch
556/// after the server answered, so a tile drawn in between is named with that frame and the
557/// next, which is the first that holds it.
558pub(crate) struct FramebufferGrab {
559    pub fb: SharedFramebuffer,
560    pending: Vec<Vec<u64>>,
561    drawn: Vec<u64>,
562    /// The tiles the last XShm grab took, named again with the frame after it.
563    carry: Vec<u64>,
564}
565
566impl FramebufferGrab {
567    /// A grab for `surfaces` pool surfaces, each missing the whole screen.
568    pub(crate) fn new(fb: SharedFramebuffer, surfaces: usize) -> Self {
569        let words = fb.words();
570        Self {
571            fb,
572            pending: vec![vec![u64::MAX; words]; surfaces],
573            drawn: vec![u64::MAX; words],
574            carry: vec![0; words],
575        }
576    }
577
578    /// Take the tiles drawn since the last take into every surface's and into `drawn`.
579    fn take(&mut self) {
580        let (pending, drawn) = (&mut self.pending, &mut self.drawn);
581        self.fb.take(|i, bits| {
582            for p in pending.iter_mut() {
583                p[i] |= bits;
584            }
585            drawn[i] |= bits;
586        });
587    }
588
589    /// Where the frame just read differs from the one before, within the region at (`x`, `y`)
590    /// of `width` x `height`, in its coordinates: a rectangle a run of tiles along a row of
591    /// them. The tiles are those the reads since the last call took (the type's docblock), so
592    /// this is called once after each frame's read.
593    pub(crate) fn changed(
594        &mut self,
595        (x, y, width, height): (u32, u32, u32, u32),
596    ) -> Vec<smithay::utils::Rectangle<i32, smithay::utils::Physical>> {
597        let tile = self.fb.tile;
598        let tiles_x = self.fb.tiles_x as usize;
599        let drawn = std::mem::replace(&mut self.drawn, std::mem::take(&mut self.carry));
600        self.carry = vec![0; self.fb.words()];
601        let set = |tx: u32, ty: u32| {
602            let t = ty as usize * tiles_x + tx as usize;
603            drawn[t / 64] >> (t % 64) & 1 != 0
604        };
605        let (tx0, tx1) = (x / tile, (x + width).div_ceil(tile).min(self.fb.tiles_x));
606        let (ty0, ty1) = (y / tile, (y + height).div_ceil(tile).min(self.fb.tiles_y));
607        let mut rects = Vec::new();
608        for ty in ty0..ty1 {
609            let mut tx = tx0;
610            while tx < tx1 {
611                if !set(tx, ty) {
612                    tx += 1;
613                    continue;
614                }
615                let run = tx;
616                while tx < tx1 && set(tx, ty) {
617                    tx += 1;
618                }
619                let left = (run * tile).max(x);
620                let right = (tx * tile).min(x + width);
621                let top = (ty * tile).max(y);
622                let bottom = ((ty + 1) * tile).min(y + height);
623                rects.push(smithay::utils::Rectangle::new(
624                    ((left - x) as i32, (top - y) as i32).into(),
625                    ((right - left) as i32, (bottom - top) as i32).into(),
626                ));
627            }
628        }
629        rects
630    }
631
632    /// Mark the whole screen missing from every surface and drawn since the last frame (a
633    /// region moved, the surfaces were recreated): the next frame differs from the one before
634    /// wherever it lies, which no drawing reported.
635    pub(crate) fn invalidate(&mut self) {
636        for p in self.pending.iter_mut() {
637            p.fill(u64::MAX);
638        }
639        self.drawn.fill(u64::MAX);
640    }
641
642    /// Mark the area at (`x`, `y`), `width` x `height`, in screen coordinates, missing from
643    /// surface `idx`: an overlay drawn onto it.
644    pub(crate) fn overlaid(&mut self, idx: usize, x: i32, y: i32, width: i32, height: i32) {
645        let tile = self.fb.tile as i32;
646        let (tiles_x, tiles_y) = (self.fb.tiles_x as i32, self.fb.tiles_y as i32);
647        let tx0 = x.max(0) / tile;
648        let ty0 = y.max(0) / tile;
649        let tx1 = ((x + width).max(0) as u32)
650            .div_ceil(tile as u32)
651            .min(tiles_x as u32) as i32;
652        let ty1 = ((y + height).max(0) as u32)
653            .div_ceil(tile as u32)
654            .min(tiles_y as u32) as i32;
655        let pending = &mut self.pending[idx];
656        for ty in ty0..ty1 {
657            for tx in tx0..tx1 {
658                let t = (ty * tiles_x + tx) as usize;
659                pending[t / 64] |= 1 << (t % 64);
660            }
661        }
662    }
663
664    /// Bring surface `idx` (`dst`, `dst_stride` bytes a row) up to the screen over the region at
665    /// (`x`, `y`) of `width` x `height`: false where no whole read came in `READ_ATTEMPTS`, or
666    /// the region runs past the framebuffer (a resize the capture has not followed yet), the
667    /// surface left to an XShm grab, which [`Self::grabbed`] then records.
668    pub(crate) fn read(
669        &mut self,
670        idx: usize,
671        dst: &mut [u8],
672        dst_stride: usize,
673        region: (u32, u32, u32, u32),
674    ) -> bool {
675        let (x, y, width, height) = region;
676        if x + width > self.fb.width || y + height > self.fb.height {
677            return false;
678        }
679        self.take();
680        for _ in 0..READ_ATTEMPTS {
681            let Some(before) = self.fb.settled_seq() else {
682                continue;
683            };
684            self.fb
685                .copy_tiles(&self.pending[idx], dst, dst_stride, region);
686            fence(Ordering::Acquire);
687            if self.fb.framebuffer.word64(HEAD_SEQ).load(Ordering::Acquire) == before {
688                self.pending[idx].fill(0);
689                return true;
690            }
691            self.take();
692        }
693        false
694    }
695
696    /// [`Self::read`] into the planes of surface `idx` (`planes`) instead of its pixels: the
697    /// tiles it misses converted from the screen under the same drawing count, false where no
698    /// whole conversion came in `READ_ATTEMPTS` or the region is not one the planes can hold
699    /// (an odd edge, planes of another size, past the framebuffer, or a last row that starts
700    /// inside it and so ends short of a whole stride).
701    pub(crate) fn convert(
702        &mut self,
703        idx: usize,
704        planes: &mut Nv12Planes,
705        region: (u32, u32, u32, u32),
706        bands: usize,
707    ) -> bool {
708        let (x, y, width, height) = region;
709        if x + width > self.fb.width
710            || y + height > self.fb.height
711            || !(x | y | width | height).is_multiple_of(2)
712            || (y + height == self.fb.height && x > 0)
713            || (planes.width, planes.height) != (width as usize, height as usize)
714        {
715            return false;
716        }
717        self.take();
718        for _ in 0..READ_ATTEMPTS {
719            let Some(before) = self.fb.settled_seq() else {
720                continue;
721            };
722            if !self
723                .fb
724                .convert_tiles(&self.pending[idx], planes, region, bands)
725            {
726                return false;
727            }
728            fence(Ordering::Acquire);
729            if self.fb.framebuffer.word64(HEAD_SEQ).load(Ordering::Acquire) == before {
730                self.pending[idx].fill(0);
731                return true;
732            }
733            self.take();
734        }
735        false
736    }
737
738    /// Surface `idx` was grabbed whole through XShm: it misses nothing drawn before the server
739    /// answered. The tiles taken here may have been drawn after that answer, so they stay
740    /// missing from the surface and are named with the next frame as well (`carry`).
741    pub(crate) fn grabbed(&mut self, idx: usize) {
742        self.pending[idx].fill(0);
743        let (pending, drawn, carry) = (&mut self.pending, &mut self.drawn, &mut self.carry);
744        self.fb.take(|i, bits| {
745            for p in pending.iter_mut() {
746                p[i] |= bits;
747            }
748            drawn[i] |= bits;
749            carry[i] |= bits;
750        });
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757
758    /// A framebuffer and watch in anonymous memory, `width` x `height` with `tile`-pixel tiles,
759    /// the pixels each their own index.
760    fn fake(width: u32, height: u32, tile: u32) -> SharedFramebuffer {
761        let anon = |len: usize| {
762            // SAFETY: a memfd of this process, sized before it is mapped.
763            let fd = unsafe {
764                let fd = crate::memfd_create(c"test", 0);
765                assert!(fd >= 0);
766                assert_eq!(libc::ftruncate(fd, len as i64), 0);
767                <OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(fd)
768            };
769            let map = Mapping::new(&fd, len, true).unwrap();
770            (fd, map)
771        };
772        let stride = width as usize * 4;
773        let (_, framebuffer) = anon(4096 + stride * height as usize);
774        let tiles_x = width.div_ceil(tile);
775        let tiles_y = height.div_ceil(tile);
776        let (_, watch) = anon(WATCH_BITS + (tiles_x * tiles_y).div_ceil(64) as usize * 8);
777        let (event, _) = anon(8);
778        for i in 0..(width * height) as usize {
779            framebuffer
780                .word(4096 + i * 4)
781                .store(i as u32, Ordering::Relaxed);
782        }
783        SharedFramebuffer {
784            opcode: 0,
785            id: 0,
786            framebuffer,
787            watch,
788            event,
789            width,
790            height,
791            stride,
792            pixel_offset: 4096,
793            tile,
794            tiles_x,
795            tiles_y,
796        }
797    }
798
799    fn pixel(dst: &[u8], stride: usize, x: usize, y: usize) -> u32 {
800        u32::from_ne_bytes(dst[y * stride + x * 4..][..4].try_into().unwrap())
801    }
802
803    #[test]
804    fn a_surface_takes_the_tiles_it_misses_within_its_region() {
805        let mut grab = FramebufferGrab::new(fake(64, 48, 16), 2);
806        let (w, h) = (40usize, 30usize);
807        let mut dst = vec![0u8; w * h * 4];
808        assert!(grab.read(0, &mut dst, w * 4, (8, 10, w as u32, h as u32)));
809        for y in 0..h {
810            for x in 0..w {
811                assert_eq!(pixel(&dst, w * 4, x, y), ((y + 10) * 64 + x + 8) as u32);
812            }
813        }
814        dst.fill(0);
815        grab.fb
816            .watch
817            .word64(WATCH_BITS)
818            .store(1 << 5, Ordering::Relaxed);
819        assert!(grab.read(0, &mut dst, w * 4, (8, 10, w as u32, h as u32)));
820        let copied = (0..h)
821            .flat_map(|y| (0..w).map(move |x| (x, y)))
822            .filter(|&(x, y)| pixel(&dst, w * 4, x, y) != 0)
823            .count();
824        assert_eq!(
825            copied,
826            16 * 16,
827            "tile 5, x 16..32 and y 16..32, within the region"
828        );
829        let mut other = vec![0u8; w * h * 4];
830        assert!(grab.read(1, &mut other, w * 4, (8, 10, w as u32, h as u32)));
831        assert_eq!(
832            pixel(&other, w * 4, 0, 0),
833            (10 * 64 + 8) as u32,
834            "a surface that held nothing yet takes the whole region"
835        );
836    }
837
838    /// The planes `copy` would give, converting the pixels a read copied out.
839    fn converted_copy(grab: &mut FramebufferGrab, region: (u32, u32, u32, u32)) -> Nv12Planes {
840        let (w, h) = (region.2 as usize, region.3 as usize);
841        let mut dst = vec![0u8; w * h * 4];
842        assert!(grab.read(0, &mut dst, w * 4, region));
843        let mut planes = Nv12Planes::new(w, h);
844        let strides = (planes.stride[0], planes.stride[1]);
845        let (y, uv) = planes.planes_mut();
846        crate::encoders::software::convert_to_nv12_mt(
847            &dst,
848            (w * 4) as u32,
849            w,
850            h,
851            false,
852            y,
853            uv,
854            strides,
855            1,
856        )
857        .unwrap();
858        planes
859    }
860
861    #[test]
862    fn converting_in_place_gives_the_planes_a_copy_converted_gives() {
863        let region = (8, 16, 48, 32);
864        let expected = converted_copy(&mut FramebufferGrab::new(fake(64, 64, 16), 1), region);
865        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 1);
866        let mut planes = Nv12Planes::new(48, 32);
867        assert!(grab.convert(0, &mut planes, region, 2));
868        assert!(planes.planes() == expected.planes());
869    }
870
871    #[test]
872    fn a_conversion_takes_only_the_rows_of_tiles_drawn() {
873        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 1);
874        let mut planes = Nv12Planes::new(64, 48);
875        assert!(grab.convert(0, &mut planes, (0, 0, 64, 48), 2));
876        planes.planes_mut().0.fill(0);
877        grab.fb
878            .watch
879            .word64(WATCH_BITS)
880            .store(1 << 5, Ordering::Relaxed);
881        assert!(grab.convert(0, &mut planes, (0, 0, 64, 48), 2));
882        let stride = planes.stride[0];
883        for (row, line) in planes.planes().0.chunks(stride).enumerate() {
884            assert_eq!(
885                line[..64].iter().any(|&l| l != 0),
886                (16..32).contains(&row),
887                "tile 5 lies in rows 16..32, converted across the region's width (row {row})"
888            );
889        }
890    }
891
892    #[test]
893    fn a_conversion_refuses_what_its_planes_cannot_hold() {
894        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 1);
895        let mut planes = Nv12Planes::new(64, 64);
896        assert!(
897            !grab.convert(0, &mut planes, (1, 0, 32, 32), 1),
898            "an odd edge"
899        );
900        assert!(
901            !grab.convert(0, &mut planes, (8, 0, 32, 64), 1),
902            "a last row short of the stride"
903        );
904        assert!(
905            !grab.convert(0, &mut planes, (0, 0, 80, 32), 1),
906            "past the framebuffer"
907        );
908    }
909
910    #[test]
911    fn a_read_an_operation_runs_across_is_not_taken() {
912        let mut grab = FramebufferGrab::new(fake(32, 32, 16), 1);
913        grab.fb
914            .framebuffer
915            .word64(HEAD_SEQ)
916            .store(1, Ordering::Relaxed);
917        let mut dst = vec![0u8; 32 * 32 * 4];
918        assert!(
919            !grab.read(0, &mut dst, 32 * 4, (0, 0, 32, 32)),
920            "a drawing operation that never ends leaves the frame to XShm"
921        );
922        grab.grabbed(0);
923        assert!(grab.pending[0].iter().all(|&w| w == 0));
924    }
925
926    #[test]
927    fn a_frame_says_where_the_screen_was_drawn_within_its_region() {
928        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 1);
929        let mut dst = vec![0u8; 64 * 64 * 4];
930        assert!(grab.read(0, &mut dst, 256, (8, 8, 48, 48)));
931        assert_eq!(
932            grab.changed((8, 8, 48, 48)).len(),
933            4,
934            "a first frame changed whole, a run of tiles a row"
935        );
936        grab.fb
937            .watch
938            .word64(WATCH_BITS)
939            .store(0b0110 << 4, Ordering::Relaxed);
940        assert!(grab.read(0, &mut dst, 256, (8, 8, 48, 48)));
941        let rects = grab.changed((8, 8, 48, 48));
942        assert_eq!(
943            rects,
944            vec![smithay::utils::Rectangle::new(
945                (8, 8).into(),
946                (32, 16).into()
947            )],
948            "tiles (1,1) and (2,1), at 16..48 by 16..32, in the region's coordinates"
949        );
950        assert!(grab.read(0, &mut dst, 256, (8, 8, 48, 48)));
951        assert!(grab.changed((8, 8, 48, 48)).is_empty());
952    }
953
954    #[test]
955    fn a_tile_taken_with_an_xshm_grab_is_named_with_the_next_frame_too() {
956        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 2);
957        let region = (0, 0, 64, 64);
958        let mut b = vec![0u8; 64 * 64 * 4];
959        grab.grabbed(0);
960        grab.changed(region);
961        grab.fb
962            .watch
963            .word64(WATCH_BITS)
964            .store(1 << 5, Ordering::Relaxed);
965        grab.grabbed(0);
966        let with_grab = grab.changed(region);
967        assert!(grab.read(1, &mut b, 256, region));
968        let after = grab.changed(region);
969        let covers = |rects: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>]| {
970            rects.iter().any(|r| {
971                r.loc.x <= 24 && 24 < r.loc.x + r.size.w && r.loc.y <= 24 && 24 < r.loc.y + r.size.h
972            })
973        };
974        assert!(covers(&with_grab), "named with the grabbed frame");
975        assert!(
976            covers(&after),
977            "and with the next, the first the tile is known to be in"
978        );
979        assert!(
980            grab.pending[0][0] >> 5 & 1 != 0,
981            "the grabbed surface still misses it, as the grab may predate it"
982        );
983        assert!(grab.read(0, &mut b, 256, region));
984        assert!(
985            grab.changed(region).is_empty(),
986            "and it is named no further"
987        );
988    }
989
990    #[test]
991    fn a_tile_drawn_after_a_read_is_reported_with_the_frame_that_holds_it() {
992        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 2);
993        let region = (0, 0, 64, 64);
994        let mut a = vec![0u8; 64 * 64 * 4];
995        let mut b = vec![0u8; 64 * 64 * 4];
996        assert!(grab.read(0, &mut a, 256, region));
997        grab.changed(region);
998        assert!(grab.read(1, &mut b, 256, region));
999        grab.changed(region);
1000        assert!(grab.read(0, &mut a, 256, region));
1001        grab.fb
1002            .watch
1003            .word64(WATCH_BITS)
1004            .store(1 << 5, Ordering::Relaxed);
1005        let before = grab.changed(region);
1006        assert!(grab.read(1, &mut b, 256, region));
1007        let after = grab.changed(region);
1008        let covers = |rects: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>]| {
1009            rects.iter().any(|r| {
1010                r.loc.x <= 24 && 24 < r.loc.x + r.size.w && r.loc.y <= 24 && 24 < r.loc.y + r.size.h
1011            })
1012        };
1013        assert!(
1014            covers(&after),
1015            "tile (1,1), drawn after surface 0 was read, is reported with surface 1, which holds it"
1016        );
1017        assert!(!covers(&before), "and not with surface 0, which does not");
1018    }
1019
1020    #[test]
1021    fn an_overlay_marks_its_tiles_missing() {
1022        let mut grab = FramebufferGrab::new(fake(64, 64, 16), 1);
1023        grab.grabbed(0);
1024        grab.overlaid(0, 20, 30, 10, 10);
1025        let set: Vec<usize> = (0..16)
1026            .filter(|&t| grab.pending[0][0] >> t & 1 != 0)
1027            .collect();
1028        assert_eq!(set, vec![5, 9], "tiles (1,1) and (1,2)");
1029    }
1030}