1use 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
62const 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
70const 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
77const READ_ATTEMPTS: u32 = 4;
79
80const PARALLEL_BYTES: usize = 1 << 20;
85
86const COPY_THREADS: usize = 4;
89
90const DRAW_WAIT: Duration = Duration::from_millis(2);
93
94struct 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 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 fn word(&self, offset: usize) -> &AtomicU32 {
126 assert!(offset.is_multiple_of(4) && offset + 4 <= self.len);
127 unsafe { AtomicU32::from_ptr(self.ptr.add(offset).cast()) }
130 }
131
132 fn word64(&self, offset: usize) -> &AtomicU64 {
134 assert!(offset.is_multiple_of(8) && offset + 8 <= self.len);
135 unsafe { AtomicU64::from_ptr(self.ptr.add(offset).cast()) }
137 }
138}
139
140impl Drop for Mapping {
141 fn drop(&mut self) {
142 unsafe {
144 libc::munmap(self.ptr.cast(), self.len);
145 }
146 }
147}
148
149struct 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
198pub(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
214pub(crate) enum Opened {
216 Watch(SharedFramebuffer),
217 Absent,
219 Declined(String),
221}
222
223impl SharedFramebuffer {
224 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 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 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 pub(crate) fn retired(&self) -> bool {
318 self.framebuffer.word(HEAD_RETIRED).load(Ordering::Acquire) != 0
319 }
320
321 fn words(&self) -> usize {
323 (self.tiles_x as usize * self.tiles_y as usize).div_ceil(64)
324 }
325
326 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 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 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 let ready = unsafe { libc::ppoll(&mut poll, 1, &deadline, std::ptr::null()) } > 0;
369 if ready {
370 let mut count = 0u64;
371 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 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 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 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 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 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
547pub(crate) struct FramebufferGrab {
559 pub fb: SharedFramebuffer,
560 pending: Vec<Vec<u64>>,
561 drawn: Vec<u64>,
562 carry: Vec<u64>,
564}
565
566impl FramebufferGrab {
567 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 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 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 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 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 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 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 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 fn fake(width: u32, height: u32, tile: u32) -> SharedFramebuffer {
761 let anon = |len: usize| {
762 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 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}