1use std::cell::Cell;
34use std::os::unix::io::AsRawFd;
35use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering};
36use std::sync::mpsc::Sender;
37use std::sync::{Arc, Condvar, Mutex};
38use std::thread;
39use std::time::{Duration, Instant};
40
41use x11rb::connection::Connection;
42use x11rb::protocol::Event;
43use x11rb::protocol::damage::{ConnectionExt as DamageExt, Damage, ReportLevel};
44use x11rb::protocol::shm::ConnectionExt as ShmExt;
45use x11rb::protocol::xfixes::{ConnectionExt as XfixesExt, Region};
46use x11rb::protocol::xproto::{
47 Atom, AtomEnum, ConnectionExt as XprotoExt, ImageFormat, PropMode, Rectangle, Window,
48};
49use x11rb::rust_connection::RustConnection;
50use x11rb::wrapper::ConnectionExt as WrapperExt;
51
52use crate::pace::{FramePace, TickTrigger};
53
54use crate::CaptureSettings;
55use crate::encoders::overlay::blend_pixel_premultiplied;
56use crate::encoders::software::{EncodedStripe, FrameTiming, Nv12Planes};
57use crate::pipeline::X11Pipeline;
58use crate::recording_sink::RecordingSink;
59
60pub mod computer_use;
61pub mod cursor;
62pub mod dri3;
63mod framebuffer;
64pub mod nvfbc;
65
66use framebuffer::{FramebufferGrab, Opened, SharedFramebuffer};
67
68pub struct Controls {
92 pub stop: AtomicBool,
93 pub finished: AtomicBool,
98 pub force_idr: AtomicBool,
99 pub reference_reports: Mutex<Vec<crate::encoders::reference::ReferenceReport>>,
100 pub rate_dirty: AtomicBool,
101 pub bitrate_kbps: AtomicI32,
102 pub vbv_mult_milli: AtomicI32,
103 pub fps_bits: AtomicU64,
104 pub link_kbps: AtomicI32,
105 pub tunables_dirty: AtomicBool,
106 pub tunables: Mutex<Option<crate::LiveTunables>>,
107 pub capture_cursor: AtomicBool,
108 pub codec: AtomicU32,
111 pub takes_planes: AtomicBool,
115 pub region_dirty: AtomicBool,
116 pub region: Mutex<(i32, i32, i32, i32)>,
117 pub report: Arc<crate::report::StreamReport>,
119}
120
121impl Controls {
122 pub fn new(s: &CaptureSettings) -> Self {
125 Self {
126 stop: AtomicBool::new(false),
127 finished: AtomicBool::new(false),
128 force_idr: AtomicBool::new(false),
129 reference_reports: Mutex::new(Vec::new()),
130 rate_dirty: AtomicBool::new(false),
131 bitrate_kbps: AtomicI32::new(s.video_bitrate_kbps),
132 vbv_mult_milli: AtomicI32::new((s.video_vbv_multiplier * 1000.0).round() as i32),
133 fps_bits: AtomicU64::new(s.target_fps.max(1.0).to_bits()),
134 link_kbps: AtomicI32::new(s.video_link_kbps),
135 tunables_dirty: AtomicBool::new(false),
136 tunables: Mutex::new(None),
137 capture_cursor: AtomicBool::new(s.capture_cursor),
138 codec: AtomicU32::new(u32::MAX),
139 takes_planes: AtomicBool::new(false),
140 region_dirty: AtomicBool::new(false),
141 region: Mutex::new((s.capture_x, s.capture_y, s.width, s.height)),
142 report: crate::report::StreamReport::new("x11"),
143 }
144 }
145}
146
147struct ShmSurface {
156 shmseg: u32,
157 addr: *mut u8,
158 size: usize,
159 width: u16,
160 height: u16,
161 stride: usize,
162}
163
164impl ShmSurface {
165 fn create(conn: &RustConnection, width: u16, height: u16) -> Result<Self, String> {
176 let stride = width as usize * 4;
177 let size = stride * height as usize;
178 if size == 0 {
179 return Err("zero-sized capture surface".into());
180 }
181 unsafe {
182 let shmid = libc::shmget(libc::IPC_PRIVATE, size, libc::IPC_CREAT | 0o600);
183 if shmid < 0 {
184 return Err("shmget failed".into());
185 }
186 let addr = libc::shmat(shmid, std::ptr::null(), 0);
187 if addr == (-1isize) as *mut libc::c_void {
188 libc::shmctl(shmid, libc::IPC_RMID, std::ptr::null_mut());
189 return Err("shmat failed".into());
190 }
191 let shmseg = match conn.generate_id() {
192 Ok(id) => id,
193 Err(e) => {
194 libc::shmdt(addr);
195 libc::shmctl(shmid, libc::IPC_RMID, std::ptr::null_mut());
196 return Err(format!("generate_id: {e}"));
197 }
198 };
199 let attach = conn
200 .shm_attach(shmseg, shmid as u32, false)
201 .map_err(|e| format!("shm_attach: {e}"))
202 .and_then(|c| c.check().map_err(|e| format!("shm_attach check: {e}")));
203 libc::shmctl(shmid, libc::IPC_RMID, std::ptr::null_mut());
204 if let Err(e) = attach {
205 libc::shmdt(addr);
206 return Err(e);
207 }
208 Ok(Self {
209 shmseg,
210 addr: addr as *mut u8,
211 size,
212 width,
213 height,
214 stride,
215 })
216 }
217 }
218
219 fn as_mut_slice(&mut self) -> &mut [u8] {
221 unsafe { std::slice::from_raw_parts_mut(self.addr, self.size) }
222 }
223
224 fn as_slice(&self) -> &[u8] {
226 unsafe { std::slice::from_raw_parts(self.addr, self.size) }
227 }
228
229 fn destroy(&mut self, conn: &RustConnection) {
237 let _ = conn.shm_detach(self.shmseg);
238 let _ = conn.flush();
239 unsafe {
240 libc::shmdt(self.addr as *mut libc::c_void);
241 }
242 self.addr = std::ptr::null_mut();
243 }
244}
245
246fn require_32bpp(conn: &RustConnection, screen: usize) -> Result<(), String> {
253 let root_depth = conn.setup().roots[screen].root_depth;
254 let bpp = conn
255 .setup()
256 .pixmap_formats
257 .iter()
258 .find(|f| f.depth == root_depth)
259 .map(|f| f.bits_per_pixel)
260 .unwrap_or(32);
261 if bpp != 32 {
262 return Err(format!(
263 "unsupported root depth {root_depth} ({bpp} bpp); only 32-bpp BGRA is supported"
264 ));
265 }
266 Ok(())
267}
268
269fn clamp_offset(x: i32, root: u16) -> i16 {
274 let upper = i32::from(root)
275 .saturating_sub(1)
276 .min(i32::from(i16::MAX) - 1);
277 x.max(0).min(upper) as i16
278}
279
280fn resolve_dims(root_w: u16, root_h: u16, s: &CaptureSettings) -> (u16, u16) {
288 let cap_x = i32::from(clamp_offset(s.capture_x, root_w));
289 let cap_y = i32::from(clamp_offset(s.capture_y, root_h));
290 let avail_w = i32::from(root_w).saturating_sub(cap_x).max(2);
291 let avail_h = i32::from(root_h).saturating_sub(cap_y).max(2);
292 let mut w = if s.auto_adjust_screen_capture_size || s.width <= 0 {
293 avail_w
294 } else {
295 s.width.min(avail_w)
296 };
297 let mut h = if s.auto_adjust_screen_capture_size || s.height <= 0 {
298 avail_h
299 } else {
300 s.height.min(avail_h)
301 };
302 w = w.clamp(2, i32::from(u16::MAX));
303 h = h.clamp(2, i32::from(u16::MAX));
304 if s.codec.is_video() {
305 w &= !1;
306 h &= !1;
307 }
308 (w as u16, h as u16)
309}
310
311#[allow(clippy::too_many_arguments)]
319fn try_rebuild_channel(
320 conn: &mut RustConnection,
321 root: &mut u32,
322 rsettings: &CaptureSettings,
323 cap_x: &mut i16,
324 cap_y: &mut i16,
325 cap_w: &mut u16,
326 cap_h: &mut u16,
327 root_w: &mut u16,
328 root_h: &mut u16,
329 pool_n: usize,
330 pool: &FramePool,
331 surfaces: &mut Vec<ShmSurface>,
332 stop: &AtomicBool,
333) -> Result<(), String> {
334 let (new_conn, new_screen) =
335 x11rb::connect(None).map_err(|e| format!("X11 reconnect failed: {e}"))?;
336 require_32bpp(&new_conn, new_screen)?;
337 new_conn
338 .shm_query_version()
339 .map_err(|e| format!("shm_query_version: {e}"))?
340 .reply()
341 .map_err(|e| format!("XShm unavailable on reconnect: {e}"))?;
342 new_conn
343 .xfixes_query_version(5, 0)
344 .map_err(|e| format!("xfixes_query_version: {e}"))?
345 .reply()
346 .map_err(|e| format!("XFixes unavailable on reconnect: {e}"))?;
347 let new_root = new_conn.setup().roots[new_screen].root;
348 let geo = new_conn
349 .get_geometry(new_root)
350 .map_err(|e| format!("get_geometry: {e}"))?
351 .reply()
352 .map_err(|e| format!("get_geometry reply on reconnect: {e}"))?;
353 if !pool.drain_for_resize(pool_n, stop) {
354 return Err("pool drain interrupted by stop during reconnect".to_string());
355 }
356 let (fw, fh) = resolve_dims(geo.width, geo.height, rsettings);
357 let mut fresh: Vec<ShmSurface> = Vec::with_capacity(pool_n);
358 for _ in 0..pool_n {
359 match ShmSurface::create(&new_conn, fw, fh) {
360 Ok(s) => fresh.push(s),
361 Err(e) => {
362 for s in fresh.iter_mut() {
363 s.destroy(&new_conn);
364 }
365 return Err(e);
366 }
367 }
368 }
369 for s in surfaces.iter_mut() {
370 s.destroy(conn);
371 }
372 *conn = new_conn;
373 *root = new_root;
374 *cap_x = clamp_offset(rsettings.capture_x, geo.width);
375 *cap_y = clamp_offset(rsettings.capture_y, geo.height);
376 *cap_w = fw;
377 *cap_h = fh;
378 *root_w = geo.width;
379 *root_h = geo.height;
380 *surfaces = fresh;
381 pool.bump_generation();
382 Ok(())
383}
384
385struct RootDamage {
392 id: Damage,
393 parts: Option<Region>,
396 reported: Cell<bool>,
399}
400
401impl RootDamage {
402 fn create(conn: &RustConnection, root: Window) -> Option<Self> {
403 conn.damage_query_version(1, 1).ok()?.reply().ok()?;
404 let id = conn.generate_id().ok()?;
405 conn.damage_create(id, root, ReportLevel::NON_EMPTY)
406 .ok()?
407 .check()
408 .ok()?;
409 let parts = (|| {
410 if conn
411 .xfixes_query_version(2, 0)
412 .ok()?
413 .reply()
414 .ok()?
415 .major_version
416 < 2
417 {
418 return None;
419 }
420 let parts = conn.generate_id().ok()?;
421 conn.xfixes_create_region(parts, &[]).ok()?.check().ok()?;
422 Some(parts)
423 })();
424 Some(Self {
425 id,
426 parts,
427 reported: Cell::new(false),
428 })
429 }
430
431 fn reported(&self, conn: &RustConnection) -> bool {
434 while let Ok(Some(event)) = conn.poll_for_event() {
435 if matches!(event, Event::DamageNotify(_)) {
436 self.reported.set(true);
437 }
438 }
439 self.reported.get()
440 }
441
442 fn clear(&self, conn: &RustConnection) {
447 if !self.reported(conn) {
448 return;
449 }
450 self.reported.set(false);
451 let _ = conn.damage_subtract(self.id, x11rb::NONE, x11rb::NONE);
452 let _ = conn.flush();
453 }
454
455 fn take(&self, conn: &RustConnection) -> Option<Vec<Rectangle>> {
458 if !self.reported(conn) {
459 return Some(Vec::new());
460 }
461 let Some(parts) = self.parts else {
462 self.clear(conn);
463 return None;
464 };
465 self.reported.set(false);
466 conn.damage_subtract(self.id, x11rb::NONE, parts).ok()?;
467 Some(
468 conn.xfixes_fetch_region(parts)
469 .ok()?
470 .reply()
471 .ok()?
472 .rectangles,
473 )
474 }
475}
476
477fn take_tick(
482 damage: &RootDamage,
483 conn: &RustConnection,
484 trigger: TickTrigger,
485) -> (bool, Option<Vec<Rectangle>>) {
486 let rects = damage.take(conn);
487 let dirty =
488 trigger == TickTrigger::Damage || rects.as_ref().is_none_or(|rects| !rects.is_empty());
489 (dirty, rects)
490}
491
492fn captured_damage(
495 rects: &[Rectangle],
496 x: i32,
497 y: i32,
498 width: i32,
499 height: i32,
500) -> crate::pipeline::Damage {
501 let local: Vec<smithay::utils::Rectangle<i32, smithay::utils::Physical>> = rects
502 .iter()
503 .map(|r| {
504 smithay::utils::Rectangle::new(
505 (i32::from(r.x) - x, i32::from(r.y) - y).into(),
506 (i32::from(r.width), i32::from(r.height)).into(),
507 )
508 })
509 .collect();
510 crate::pipeline::Damage::of_rects(&local, width, height)
511}
512
513pub(crate) fn frame_rects(
516 rects: &[Rectangle],
517 x: i16,
518 y: i16,
519 width: u16,
520 height: u16,
521) -> Vec<smithay::utils::Rectangle<i32, smithay::utils::Physical>> {
522 rects
523 .iter()
524 .filter_map(|r| {
525 let left = (i32::from(r.x) - i32::from(x)).max(0);
526 let top = (i32::from(r.y) - i32::from(y)).max(0);
527 let right = (i32::from(r.x) + i32::from(r.width) - i32::from(x)).min(width.into());
528 let bottom = (i32::from(r.y) + i32::from(r.height) - i32::from(y)).min(height.into());
529 (right > left && bottom > top).then(|| {
530 smithay::utils::Rectangle::new(
531 (left, top).into(),
532 (right - left, bottom - top).into(),
533 )
534 })
535 })
536 .collect()
537}
538
539fn readable(conn: &RustConnection, timeout: Duration) -> bool {
541 let mut poll = libc::pollfd {
542 fd: conn.stream().as_raw_fd(),
543 events: libc::POLLIN,
544 revents: 0,
545 };
546 let deadline = libc::timespec {
547 tv_sec: timeout.as_secs() as _,
548 tv_nsec: libc::c_long::from(timeout.subsec_nanos()),
549 };
550 unsafe { libc::ppoll(&mut poll, 1, &deadline, std::ptr::null()) > 0 }
551}
552
553fn wait_for_frame(
559 conn: &RustConnection,
560 damage: Option<&RootDamage>,
561 pace: &FramePace,
562 period: Duration,
563) -> TickTrigger {
564 let now = Instant::now();
565 let due = pace.next_due(period, now);
566 let sleep_until = |t: Instant| {
567 if let Some(wait) = t.checked_duration_since(Instant::now()) {
568 thread::sleep(wait);
569 }
570 };
571 let Some(damage) = damage else {
572 sleep_until(due);
573 return TickTrigger::Timer;
574 };
575 sleep_until(pace.pull_at(TickTrigger::Damage, period, now).min(due));
578 loop {
579 if damage.reported(conn) {
580 return TickTrigger::Damage;
581 }
582 let Some(wait) = due.checked_duration_since(Instant::now()) else {
583 return TickTrigger::Timer;
584 };
585 if !readable(conn, wait) {
586 return TickTrigger::Timer;
587 }
588 }
589}
590
591fn wait_for_framebuffer(fb: &SharedFramebuffer, pace: &FramePace, period: Duration) -> TickTrigger {
595 let now = Instant::now();
596 let due = pace.next_due(period, now);
597 if let Some(wait) = pace
598 .pull_at(TickTrigger::Damage, period, now)
599 .min(due)
600 .checked_duration_since(Instant::now())
601 {
602 thread::sleep(wait);
603 }
604 match due.checked_duration_since(Instant::now()) {
605 Some(wait) if fb.wait(wait) => TickTrigger::Damage,
606 None if fb.wait(Duration::ZERO) => TickTrigger::Damage,
607 _ => TickTrigger::Timer,
608 }
609}
610
611fn open_framebuffer(
616 conn: &RustConnection,
617 root: Window,
618 surface: &mut ShmSurface,
619 region: (i16, i16, u16, u16),
620 surfaces: usize,
621) -> Option<FramebufferGrab> {
622 let fb = match SharedFramebuffer::open(conn, root) {
623 Opened::Watch(fb) => fb,
624 Opened::Absent => return None,
625 Opened::Declined(reason) => {
626 println!("[X11] Shared framebuffer not used: {reason}");
627 crate::report::capture_declined("Framebuffer", &reason);
628 return None;
629 }
630 };
631 let (x, y, w, h) = region;
632 let mut grab = FramebufferGrab::new(fb, surfaces);
633 let mut read = vec![0u8; surface.size];
634 let matches = (0..3).any(|_| {
635 grab.invalidate();
636 grab_frame(conn, root, surface, x, y, w, h).is_ok()
637 && grab.read(
638 0,
639 &mut read,
640 surface.stride,
641 (x as u32, y as u32, u32::from(w), u32::from(h)),
642 )
643 && read == surface.as_slice()
644 });
645 if !matches {
646 let reason = "its pixels differ from what GetImage returns";
647 println!("[X11] Shared framebuffer not used: {reason}");
648 crate::report::capture_declined("Framebuffer", reason);
649 grab.fb.close(conn);
650 return None;
651 }
652 grab.invalidate();
653 println!(
654 "[X11] Reading the screen in place from the X server's shared framebuffer ({}x{})",
655 grab.fb.width, grab.fb.height
656 );
657 crate::report::capture("Framebuffer", false);
658 Some(grab)
659}
660
661fn grab_frame(
666 conn: &RustConnection,
667 root: u32,
668 surface: &ShmSurface,
669 cap_x: i16,
670 cap_y: i16,
671 cap_w: u16,
672 cap_h: u16,
673) -> Result<(), String> {
674 conn.shm_get_image(
675 root,
676 cap_x,
677 cap_y,
678 cap_w,
679 cap_h,
680 !0u32,
681 ImageFormat::Z_PIXMAP.into(),
682 surface.shmseg,
683 0,
684 )
685 .map_err(|e| format!("shm_get_image: {e}"))?
686 .reply()
687 .map_err(|e| format!("shm_get_image reply: {e}"))?;
688 Ok(())
689}
690
691#[inline]
698pub(crate) fn cursor_image_origin(
699 x: i16,
700 y: i16,
701 xhot: u16,
702 yhot: u16,
703 cap_x: i32,
704 cap_y: i32,
705) -> (i32, i32) {
706 (
707 i32::from(x) - i32::from(xhot) - cap_x,
708 i32::from(y) - i32::from(yhot) - cap_y,
709 )
710}
711
712#[allow(clippy::too_many_arguments)]
717pub(crate) fn overlay_cursor(
718 frame: &mut [u8],
719 stride: usize,
720 frame_w: i32,
721 frame_h: i32,
722 cur_w: i32,
723 cur_h: i32,
724 pixels: &[u32],
725 img_x: i32,
726 img_y: i32,
727) {
728 for y in 0..cur_h {
729 let ty = img_y + y;
730 if ty < 0 || ty >= frame_h {
731 continue;
732 }
733 for x in 0..cur_w {
734 let tx = img_x + x;
735 if tx < 0 || tx >= frame_w {
736 continue;
737 }
738 let px = pixels[(y * cur_w + x) as usize];
739 let a = ((px >> 24) & 0xFF) as u8;
740 let r = ((px >> 16) & 0xFF) as u8;
741 let g = ((px >> 8) & 0xFF) as u8;
742 let b = (px & 0xFF) as u8;
743 let off = ty as usize * stride + tx as usize * 4;
744 blend_pixel_premultiplied(&mut frame[off..off + 4], r, g, b, a);
745 }
746 }
747}
748
749struct RawFrame {
756 idx: usize,
757 ptr: *mut u8,
758 len: usize,
759 width: u16,
760 height: u16,
761 stride: usize,
762 generation: u64,
763 captured_ns: i64,
765 changed: Vec<smithay::utils::Rectangle<i32, smithay::utils::Physical>>,
768 complete: bool,
772 planes: Option<*const Nv12Planes>,
776}
777unsafe impl Send for RawFrame {}
781
782struct PoolInner {
787 free: Vec<usize>,
788 slot: Option<RawFrame>,
789 held: Option<usize>,
792 release: bool,
794}
795
796struct FramePool {
818 inner: Mutex<PoolInner>,
819 cv: Condvar,
820 stop: AtomicBool,
821 generation: AtomicU64,
822}
823
824impl FramePool {
825 fn new(n: usize) -> Self {
827 Self {
828 inner: Mutex::new(PoolInner {
829 free: (0..n).collect(),
830 slot: None,
831 held: None,
832 release: false,
833 }),
834 cv: Condvar::new(),
835 stop: AtomicBool::new(false),
836 generation: AtomicU64::new(0),
837 }
838 }
839
840 fn bump_generation(&self) {
844 self.generation.fetch_add(1, Ordering::Relaxed);
845 }
846
847 fn generation(&self) -> u64 {
849 self.generation.load(Ordering::Relaxed)
850 }
851
852 fn acquire(&self, stop: &AtomicBool) -> Option<usize> {
858 let mut g = self.inner.lock().unwrap();
859 loop {
860 if let Some(idx) = g.free.pop() {
861 return Some(idx);
862 }
863 if stop.load(Ordering::Relaxed) {
864 return None;
865 }
866 let (gg, _) = self.cv.wait_timeout(g, Duration::from_millis(20)).unwrap();
867 g = gg;
868 }
869 }
870
871 fn publish(&self, frame: RawFrame, stop: &AtomicBool) -> bool {
877 let mut g = self.inner.lock().unwrap();
878 loop {
879 if g.slot.is_none() {
880 g.slot = Some(frame);
881 drop(g);
882 self.cv.notify_all();
883 return true;
884 }
885 if stop.load(Ordering::Relaxed) {
886 return false;
887 }
888 let (gg, _) = self.cv.wait_timeout(g, Duration::from_millis(20)).unwrap();
889 g = gg;
890 }
891 }
892
893 fn take(&self, mut release: impl FnMut()) -> Option<RawFrame> {
900 let mut g = self.inner.lock().unwrap();
901 loop {
902 if let Some(f) = g.slot.take() {
903 return Some(f);
904 }
905 if std::mem::take(&mut g.release) {
906 drop(g);
907 release();
908 g = self.inner.lock().unwrap();
909 if let Some(idx) = g.held.take() {
910 g.free.push(idx);
911 }
912 self.cv.notify_all();
913 continue;
914 }
915 if self.stop.load(Ordering::Acquire) {
916 return None;
917 }
918 let (gg, _) = self.cv.wait_timeout(g, Duration::from_millis(20)).unwrap();
919 g = gg;
920 }
921 }
922
923 fn recycle(&self, idx: usize) {
926 self.inner.lock().unwrap().free.push(idx);
927 self.cv.notify_all();
928 }
929
930 fn settle(&self, back: &[usize], held: Option<usize>) {
933 let mut g = self.inner.lock().unwrap();
934 g.free.extend_from_slice(back);
935 g.held = held;
936 drop(g);
937 self.cv.notify_all();
938 }
939
940 fn drain_for_resize(&self, n: usize, stop: &AtomicBool) -> bool {
953 let mut g = self.inner.lock().unwrap();
954 if let Some(old) = g.slot.take() {
955 g.free.push(old.idx);
956 }
957 while g.free.len() < n {
958 if self.stop.load(Ordering::Acquire) || stop.load(Ordering::Relaxed) {
959 return false;
960 }
961 if g.held.is_some() && !g.release {
962 g.release = true;
963 self.cv.notify_all();
964 }
965 let (gg, _) = self.cv.wait_timeout(g, Duration::from_millis(20)).unwrap();
966 g = gg;
967 }
968 true
969 }
970
971 fn shutdown(&self) {
979 let g = self.inner.lock().unwrap();
980 self.stop.store(true, Ordering::Release);
981 drop(g);
982 self.cv.notify_all();
983 }
984}
985
986fn encode_loop<F>(
1018 pool: &FramePool,
1019 controls: &Controls,
1020 settings: &CaptureSettings,
1021 on_frame: &mut F,
1022) where
1023 F: FnMut(Vec<EncodedStripe>),
1024{
1025 let _report = crate::report::enter(&controls.report);
1026 let mut psettings = settings.clone();
1027 let recording_sink = RecordingSink::try_bind(&settings.recording_socket, settings.target_fps);
1028 if recording_sink.is_some() && !settings.codec.is_video() {
1029 eprintln!(
1030 "[recording_sink] recording_socket set but the codec is JPEG; no recordable video stream."
1031 );
1032 }
1033 let mut pipeline: Option<X11Pipeline> = None;
1034 let (mut pw, mut ph) = (0i32, 0i32);
1035 let mut pgen = 0u64;
1036 let mut last_log_time = Instant::now();
1037 let mut frame_count: u64 = 0;
1038 let mut stripe_count: u64 = 0;
1039
1040 let mut held: Option<(usize, *const u8)> = None;
1042
1043 while let Some(frame) = pool.take(|| {
1044 if let Some(pl) = pipeline.as_mut() {
1045 pl.release_input();
1046 }
1047 held = None;
1048 }) {
1049 let (fw, fh) = (i32::from(frame.width), i32::from(frame.height));
1050 if pipeline.is_none() || pw != fw || ph != fh || pgen != frame.generation {
1051 let size_changed = pw != fw || ph != fh;
1052 psettings.width = fw;
1053 psettings.height = fh;
1054 psettings.video_bitrate_kbps = controls.bitrate_kbps.load(Ordering::Relaxed);
1055 psettings.video_vbv_multiplier =
1056 f64::from(controls.vbv_mult_milli.load(Ordering::Relaxed)) / 1000.0;
1057 psettings.target_fps =
1058 f64::from_bits(controls.fps_bits.load(Ordering::Relaxed)).max(1.0);
1059 let reshaped = pipeline
1060 .as_mut()
1061 .is_some_and(|pl| pl.reshape(&psettings, size_changed));
1062 if !reshaped {
1063 drop(pipeline.take());
1064 pipeline = Some(X11Pipeline::new(psettings.clone()));
1065 if let Some(pl) = &pipeline {
1066 let mut log_msg = format!(
1067 "[X11] Stream settings active -> Res: {}x{} | FPS: {:.1} | Encoder: {}",
1068 psettings.width,
1069 psettings.height,
1070 psettings.target_fps,
1071 pl.encoder_name()
1072 );
1073 let fixed = pl.fixed_rate_control();
1074 let paint_over =
1075 crate::encoders::paint_over_desc(&psettings, fixed, pl.holds_quantizer());
1076 if !pl.codec().is_video() {
1077 log_msg.push_str(&format!(
1078 " | Mode: JPEG | Quality: {}",
1079 psettings.jpeg_quality
1080 ));
1081 if let Some(paint_over) = paint_over {
1082 log_msg.push_str(&format!(" | {paint_over}"));
1083 }
1084 } else {
1085 log_msg.push_str(&format!(
1086 " | Mode: {} | {}",
1087 pl.codec().display(),
1088 crate::encoders::rate_desc(&psettings, fixed, pl.capped())
1089 ));
1090 if let Some(paint_over) = paint_over {
1091 log_msg.push_str(&format!(" | {paint_over}"));
1092 }
1093 log_msg.push_str(&format!(" | Colorspace: {}", pl.colorspace_desc()));
1094 }
1095 log_msg.push_str(&format!(
1096 " | Damage Thresh: {}f | Damage Dur: {}f",
1097 psettings.damage_block_threshold, psettings.damage_block_duration
1098 ));
1099 println!("{}", log_msg);
1100 }
1101 }
1102 pw = fw;
1103 ph = fh;
1104 pgen = frame.generation;
1105 if let Some(pl) = &pipeline {
1106 controls
1107 .takes_planes
1108 .store(pl.takes_host_planes(), Ordering::Relaxed);
1109 }
1110 }
1111 let pl = pipeline.as_mut().unwrap();
1112
1113 let sink_idr = recording_sink
1116 .as_ref()
1117 .map(|s| s.should_force_idr())
1118 .unwrap_or(false);
1119 for report in std::mem::take(&mut *controls.reference_reports.lock().unwrap()) {
1120 pl.take_report(report);
1121 }
1122 if controls.force_idr.swap(false, Ordering::Relaxed) || sink_idr {
1123 pl.request_idr();
1124 }
1125 if controls.rate_dirty.swap(false, Ordering::Acquire) {
1126 let b = controls.bitrate_kbps.load(Ordering::Relaxed);
1127 let v = f64::from(controls.vbv_mult_milli.load(Ordering::Relaxed)) / 1000.0;
1128 let fps = f64::from_bits(controls.fps_bits.load(Ordering::Relaxed)).max(1.0);
1129 pl.update_rate(b, v, fps);
1130 }
1131 if controls.tunables_dirty.swap(false, Ordering::Acquire)
1132 && let Some(t) = controls.tunables.lock().unwrap().take()
1133 {
1134 t.apply_to(&mut psettings);
1135 pl.update_tunables(&t);
1136 }
1137
1138 let buf = unsafe { std::slice::from_raw_parts(frame.ptr, frame.len) };
1139 let encode_start_ns = crate::wayland::host::now_ns();
1140 pl.set_link_rate(controls.link_kbps.load(Ordering::Relaxed));
1141 let planes = frame
1145 .planes
1146 .map(|p| unsafe { &*p })
1147 .filter(|_| pl.takes_host_planes());
1148 let mut stripes = if let Some(p) = planes {
1149 pl.process_reported_planes(buf, frame.stride, p, &frame.changed)
1150 } else if frame.complete {
1151 pl.process_reported(buf, frame.stride, &frame.changed)
1152 } else {
1153 pl.process_damaged(buf, frame.stride, &frame.changed)
1154 };
1155 FrameTiming::stamp(&mut stripes, frame.captured_ns, encode_start_ns);
1156 controls.codec.store(pl.codec().id(), Ordering::Relaxed);
1157 let reading = pl.held_input();
1158 let keep = match reading {
1159 Some(id) if planes.map(Nv12Planes::id) == Some(id) => Some((frame.idx, id)),
1160 Some(id) if held.is_some_and(|(_, h)| h == id) => held,
1161 _ => None,
1162 };
1163 let mut back = Vec::with_capacity(2);
1164 for idx in std::iter::once(frame.idx).chain(held.map(|(idx, _)| idx)) {
1165 if keep.is_none_or(|(k, _)| k != idx) && !back.contains(&idx) {
1166 back.push(idx);
1167 }
1168 }
1169 pool.settle(&back, keep.map(|(idx, _)| idx));
1170 held = keep;
1171 if !stripes.is_empty() {
1172 frame_count += 1;
1173 stripe_count += stripes.len() as u64;
1174 if let Some(ref socket) = recording_sink {
1176 socket.write_frame(&stripes, psettings.width, psettings.height);
1177 }
1178 on_frame(stripes);
1179 }
1180
1181 let now = Instant::now();
1182 let elapsed = now.duration_since(last_log_time).as_secs_f64();
1183 if elapsed >= 1.0 {
1184 crate::log::debug!(
1185 "[X11] Res: {}x{} Encoder: {} EncFPS: {:.2} EncStripes/s: {:.2}",
1186 psettings.width,
1187 psettings.height,
1188 pl.encoder_name(),
1189 frame_count as f64 / elapsed,
1190 stripe_count as f64 / elapsed
1191 );
1192 frame_count = 0;
1193 stripe_count = 0;
1194 last_log_time = now;
1195 }
1196 }
1197}
1198
1199struct VblankRate {
1207 captures: Vec<Arc<Controls>>,
1208 published: u32,
1210 conn: Option<(RustConnection, Window, Atom, Atom)>,
1212}
1213
1214static VBLANK_RATE: Mutex<VblankRate> = Mutex::new(VblankRate {
1215 captures: Vec::new(),
1216 published: 0,
1217 conn: None,
1218});
1219
1220impl VblankRate {
1221 fn publish(&mut self) {
1222 let want = self
1223 .captures
1224 .iter()
1225 .map(|c| {
1226 (f64::from_bits(c.fps_bits.load(Ordering::Relaxed)) * 1000.0)
1227 .round()
1228 .clamp(1000.0, 1_000_000.0) as u32
1229 })
1230 .max()
1231 .unwrap_or(0);
1232 if want == self.published {
1233 return;
1234 }
1235 if self.conn.is_none() {
1236 self.conn = x11rb::connect(None).ok().and_then(|(conn, screen)| {
1237 let root = conn.setup().roots[screen].root;
1238 let atom =
1239 |name: &[u8]| Some(conn.intern_atom(false, name).ok()?.reply().ok()?.atom);
1240 let millihz = atom(b"_FAKE_SCREEN_MILLIHZ")?;
1241 let fps = atom(b"_FAKE_SCREEN_FPS")?;
1242 Some((conn, root, millihz, fps))
1243 });
1244 }
1245 let sent = self
1246 .conn
1247 .as_ref()
1248 .is_some_and(|(conn, root, millihz, fps)| {
1249 let queued = if want > 0 {
1250 [(*millihz, want), (*fps, (want + 500) / 1000)]
1251 .iter()
1252 .all(|&(atom, value)| {
1253 conn.change_property32(
1254 PropMode::REPLACE,
1255 *root,
1256 atom,
1257 AtomEnum::CARDINAL,
1258 &[value],
1259 )
1260 .is_ok()
1261 })
1262 } else {
1263 [*millihz, *fps]
1264 .iter()
1265 .all(|&atom| conn.delete_property(*root, atom).is_ok())
1266 };
1267 queued
1270 && conn
1271 .get_input_focus()
1272 .ok()
1273 .and_then(|c| c.reply().ok())
1274 .is_some()
1275 });
1276 self.published = if sent { want } else { 0 };
1277 if !sent || want == 0 {
1278 self.conn = None;
1279 }
1280 }
1281}
1282
1283struct VblankClaim(Arc<Controls>);
1285
1286impl VblankClaim {
1287 fn new(controls: &Arc<Controls>) -> Self {
1288 let mut rate = VBLANK_RATE.lock().unwrap();
1289 rate.captures.push(controls.clone());
1290 rate.publish();
1291 Self(controls.clone())
1292 }
1293}
1294
1295impl Drop for VblankClaim {
1296 fn drop(&mut self) {
1297 let mut rate = VBLANK_RATE.lock().unwrap();
1298 rate.captures.retain(|c| !Arc::ptr_eq(c, &self.0));
1299 rate.publish();
1300 }
1301}
1302
1303pub fn follow_frame_rate() {
1305 VBLANK_RATE.lock().unwrap().publish();
1306}
1307
1308pub fn run_capture<F>(
1323 settings: CaptureSettings,
1324 controls: Arc<Controls>,
1325 encode_tid_tx: Sender<thread::ThreadId>,
1326 mut on_frame: F,
1327) -> Result<(), String>
1328where
1329 F: FnMut(Vec<EncodedStripe>) + Send + 'static,
1330{
1331 let _report = crate::report::enter(&controls.report);
1332 let _vblank = VblankClaim::new(&controls);
1333 if let Some(result) = dri3::run_capture(
1334 settings.clone(),
1335 controls.clone(),
1336 encode_tid_tx.clone(),
1337 &mut on_frame,
1338 ) {
1339 return result;
1340 }
1341 if let Some(result) = nvfbc::run_capture(
1342 settings.clone(),
1343 controls.clone(),
1344 encode_tid_tx.clone(),
1345 &mut on_frame,
1346 ) {
1347 return result;
1348 }
1349 crate::report::capture("XShm", false);
1350 run_shm_capture(settings, controls, encode_tid_tx, on_frame)
1351}
1352
1353fn run_shm_capture<F>(
1404 settings: CaptureSettings,
1405 controls: Arc<Controls>,
1406 encode_tid_tx: Sender<thread::ThreadId>,
1407 on_frame: F,
1408) -> Result<(), String>
1409where
1410 F: FnMut(Vec<EncodedStripe>) + Send + 'static,
1411{
1412 let (mut conn, screen_num) =
1413 x11rb::connect(None).map_err(|e| format!("X11 connect failed: {e}"))?;
1414 let mut root = conn.setup().roots[screen_num].root;
1415 require_32bpp(&conn, screen_num)?;
1416
1417 conn.shm_query_version()
1418 .map_err(|e| format!("shm_query_version: {e}"))?
1419 .reply()
1420 .map_err(|e| format!("XShm unavailable: {e}"))?;
1421 conn.xfixes_query_version(5, 0)
1422 .map_err(|e| format!("xfixes_query_version: {e}"))?
1423 .reply()
1424 .map_err(|e| format!("XFixes unavailable: {e}"))?;
1425
1426 let geo = conn
1427 .get_geometry(root)
1428 .map_err(|e| format!("get_geometry: {e}"))?
1429 .reply()
1430 .map_err(|e| format!("get_geometry reply: {e}"))?;
1431
1432 let mut rsettings = settings.clone();
1433 let (mut cap_w, mut cap_h) = resolve_dims(geo.width, geo.height, &rsettings);
1434 let mut cap_x = clamp_offset(rsettings.capture_x, geo.width);
1435 let mut cap_y = clamp_offset(rsettings.capture_y, geo.height);
1436 let (mut root_w, mut root_h) = (geo.width, geo.height);
1439
1440 const POOL_N: usize = 3;
1441 let mut surfaces: Vec<ShmSurface> = Vec::with_capacity(POOL_N);
1442 for _ in 0..POOL_N {
1443 surfaces.push(ShmSurface::create(&conn, cap_w, cap_h)?);
1444 }
1445 let mut shared = open_framebuffer(
1446 &conn,
1447 root,
1448 &mut surfaces[0],
1449 (cap_x, cap_y, cap_w, cap_h),
1450 POOL_N,
1451 );
1452 let mut damage = if shared.is_none() {
1453 println!("[X11] Capturing through XShm ({cap_w}x{cap_h})");
1454 RootDamage::create(&conn, root)
1455 } else {
1456 None
1457 };
1458 let mut rewatch = false;
1461 let mut last_overlays: Vec<smithay::utils::Rectangle<i32, smithay::utils::Physical>> =
1463 Vec::new();
1464 let mut last_cursor_serial = None;
1467 let mut reconnect_streak = 0u32;
1471 let pool = Arc::new(FramePool::new(POOL_N));
1472
1473 let mut watermark = crate::encoders::overlay::OverlayState::default();
1474 if !settings.watermark_path.is_empty() {
1475 watermark.load_watermark(&settings.watermark_path, 1.0);
1476 }
1477
1478 let enc_pool = pool.clone();
1479 let enc_controls = controls.clone();
1480 let enc_settings = settings.clone();
1481 let encode_panicked = Arc::new(AtomicBool::new(false));
1482 let guard_panicked = encode_panicked.clone();
1483 let encode_thread = thread::spawn(move || {
1484 crate::boost_thread_priority(-10);
1485 let _ = encode_tid_tx.send(thread::current().id());
1486 let mut on_frame = on_frame;
1487 let guard_pool = enc_pool.clone();
1493 let guard_controls = enc_controls.clone();
1494 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1495 encode_loop(&enc_pool, &enc_controls, &enc_settings, &mut on_frame);
1496 }));
1497 if result.is_err() {
1498 eprintln!("[X11] encode thread panicked; shutting the pool to fail the capture");
1499 guard_panicked.store(true, Ordering::Release);
1500 guard_pool.shutdown();
1501 guard_controls.stop.store(true, Ordering::Relaxed);
1502 }
1503 });
1504
1505 let mut pace = FramePace::windowed();
1507 let csc_bands = crate::encoders::session::h264_fullframe_threads(&rsettings).max(1) as usize;
1510 let mut planes_pool: Vec<Nv12Planes> = Vec::new();
1511 let mut planes_dims = (0u16, 0u16);
1512 let mut planes_mode = false;
1513 const GEOMETRY_POLL_FRAMES: i32 = 30;
1515 let mut geometry_check = 0i32;
1516 let mut grab_failures = 0u32;
1517
1518 let result = (|| -> Result<(), String> {
1519 while !controls.stop.load(Ordering::Relaxed) {
1520 let fps = f64::from_bits(controls.fps_bits.load(Ordering::Relaxed)).max(1.0);
1521 let frame_dur = Duration::from_secs_f64(1.0 / fps.max(1.0));
1522 if pace.next_due(frame_dur, Instant::now()) <= Instant::now() {
1523 thread::yield_now();
1524 }
1525 let trigger = match &shared {
1526 Some(grab) => wait_for_framebuffer(&grab.fb, &pace, frame_dur),
1527 None => wait_for_frame(&conn, damage.as_ref(), &pace, frame_dur),
1528 };
1529 let woke = Instant::now();
1530 if shared.as_ref().is_some_and(|grab| grab.fb.retired()) {
1531 if let Some(grab) = shared.take() {
1532 grab.fb.close(&conn);
1533 }
1534 rewatch = true;
1535 geometry_check = 0;
1536 }
1537 pace.record_tick(trigger, frame_dur, woke, true);
1540 if controls.stop.load(Ordering::Relaxed) {
1541 break;
1542 }
1543
1544 if controls.region_dirty.swap(false, Ordering::Acquire) {
1545 let (nx, ny, nw, nh) = *controls.region.lock().unwrap();
1546 rsettings.capture_x = nx;
1547 rsettings.capture_y = ny;
1548 rsettings.width = nw;
1549 rsettings.height = nh;
1550 rsettings.auto_adjust_screen_capture_size = nw <= 0 || nh <= 0;
1554 cap_x = clamp_offset(nx, root_w);
1555 cap_y = clamp_offset(ny, root_h);
1556 if let Some(grab) = shared.as_mut() {
1557 grab.invalidate();
1558 }
1559 if let Some(g) = conn.get_geometry(root).ok().and_then(|c| c.reply().ok()) {
1560 root_w = g.width;
1561 root_h = g.height;
1562 cap_x = clamp_offset(nx, root_w);
1563 cap_y = clamp_offset(ny, root_h);
1564 let (fw, fh) = resolve_dims(g.width, g.height, &rsettings);
1565 if fw != cap_w || fh != cap_h {
1566 if !pool.drain_for_resize(POOL_N, &controls.stop) {
1567 break;
1568 }
1569 for s in surfaces.iter_mut() {
1570 s.destroy(&conn);
1571 }
1572 surfaces.clear();
1573 cap_w = fw;
1574 cap_h = fh;
1575 for _ in 0..POOL_N {
1576 surfaces.push(ShmSurface::create(&conn, cap_w, cap_h)?);
1577 }
1578 pool.bump_generation();
1579 if let Some(grab) = shared.as_mut() {
1580 grab.invalidate();
1581 }
1582 }
1583 }
1584 }
1585
1586 geometry_check = geometry_check.saturating_sub(1);
1595 if (rsettings.auto_adjust_screen_capture_size || grab_failures > 0)
1596 && geometry_check <= 0
1597 {
1598 geometry_check = GEOMETRY_POLL_FRAMES;
1599 if let Some(g) = conn.get_geometry(root).ok().and_then(|c| c.reply().ok()) {
1600 root_w = g.width;
1603 root_h = g.height;
1604 let (px, py) = (cap_x, cap_y);
1605 cap_x = clamp_offset(rsettings.capture_x, root_w);
1606 cap_y = clamp_offset(rsettings.capture_y, root_h);
1607 if (px, py) != (cap_x, cap_y)
1608 && let Some(grab) = shared.as_mut()
1609 {
1610 grab.invalidate();
1611 }
1612 let (nw, nh) = resolve_dims(g.width, g.height, &rsettings);
1613 if nw != cap_w || nh != cap_h {
1614 if !pool.drain_for_resize(POOL_N, &controls.stop) {
1615 break;
1616 }
1617 let (fw, fh) = conn
1618 .get_geometry(root)
1619 .ok()
1620 .and_then(|c| c.reply().ok())
1621 .map(|g| resolve_dims(g.width, g.height, &rsettings))
1622 .unwrap_or((nw, nh));
1623 if fw != cap_w || fh != cap_h {
1624 for s in surfaces.iter_mut() {
1625 s.destroy(&conn);
1626 }
1627 surfaces.clear();
1628 cap_w = fw;
1629 cap_h = fh;
1630 for _ in 0..POOL_N {
1631 surfaces.push(ShmSurface::create(&conn, cap_w, cap_h)?);
1632 }
1633 pool.bump_generation();
1634 if let Some(grab) = shared.as_mut() {
1635 grab.invalidate();
1636 }
1637 if let Some(grab) = shared.as_mut() {
1638 grab.invalidate();
1639 }
1640 }
1641 }
1642 }
1643 }
1644
1645 let idx = match pool.acquire(&controls.stop) {
1646 Some(i) => i,
1647 None => break,
1648 };
1649 let mut reported = None;
1650 if std::mem::take(&mut rewatch) {
1651 shared = open_framebuffer(
1652 &conn,
1653 root,
1654 &mut surfaces[idx],
1655 (cap_x, cap_y, cap_w, cap_h),
1656 POOL_N,
1657 );
1658 if shared.is_none() && damage.is_none() {
1659 damage = RootDamage::create(&conn, root);
1660 }
1661 }
1662 let use_planes = shared.is_some()
1666 && controls.takes_planes.load(Ordering::Relaxed)
1667 && !controls.capture_cursor.load(Ordering::Relaxed)
1668 && !watermark.is_active();
1669 let fresh =
1670 use_planes && (planes_pool.len() != POOL_N || planes_dims != (cap_w, cap_h));
1671 if fresh {
1672 planes_pool = (0..POOL_N)
1673 .map(|_| Nv12Planes::new(cap_w as usize, cap_h as usize))
1674 .collect();
1675 planes_dims = (cap_w, cap_h);
1676 }
1677 if fresh || use_planes != planes_mode {
1678 planes_mode = use_planes;
1679 if let Some(grab) = shared.as_mut() {
1680 grab.invalidate();
1681 }
1682 }
1683 let grab_result = {
1684 let surface = &mut surfaces[idx];
1685 let stride = surface.stride;
1686 let region = (
1687 cap_x as u32,
1688 cap_y as u32,
1689 u32::from(cap_w),
1690 u32::from(cap_h),
1691 );
1692 match shared.as_mut() {
1693 Some(grab) if use_planes => {
1694 let planes = &mut planes_pool[idx];
1695 if grab.convert(idx, planes, region, csc_bands) {
1696 Ok(())
1697 } else {
1698 grab_frame(&conn, root, surface, cap_x, cap_y, cap_w, cap_h).and_then(
1699 |()| {
1700 let strides = (planes.stride[0], planes.stride[1]);
1701 let (y, uv) = planes.planes_mut();
1702 crate::encoders::software::convert_to_nv12_mt(
1703 surface.as_mut_slice(),
1704 stride as u32,
1705 cap_w as usize,
1706 cap_h as usize,
1707 false,
1708 y,
1709 uv,
1710 strides,
1711 csc_bands,
1712 )
1713 .map_err(|e| format!("convert: {e:?}"))?;
1714 grab.grabbed(idx);
1715 Ok(())
1716 },
1717 )
1718 }
1719 }
1720 Some(grab) => {
1721 if grab.read(idx, surface.as_mut_slice(), stride, region) {
1722 Ok(())
1723 } else {
1724 grab_frame(&conn, root, surface, cap_x, cap_y, cap_w, cap_h)
1725 .map(|()| grab.grabbed(idx))
1726 }
1727 }
1728 None => {
1729 reported = damage.as_ref().and_then(|d| d.take(&conn));
1730 grab_frame(&conn, root, surface, cap_x, cap_y, cap_w, cap_h)
1731 }
1732 }
1733 };
1734 let region = (
1735 cap_x as u32,
1736 cap_y as u32,
1737 u32::from(cap_w),
1738 u32::from(cap_h),
1739 );
1740 let mut changed = match (shared.as_mut(), reported.take()) {
1741 (Some(grab), _) => grab.changed(region),
1742 (None, Some(rects)) => frame_rects(&rects, cap_x, cap_y, cap_w, cap_h),
1743 (None, None) => Vec::new(),
1744 };
1745 if let Err(e) = grab_result {
1746 pool.recycle(idx);
1750 grab_failures += 1;
1751 geometry_check = 0;
1752 if grab_failures > 120 {
1753 let mut recovered = false;
1760 for _attempt in 0..5 {
1761 if controls.stop.load(Ordering::Relaxed) {
1762 break;
1763 }
1764 std::thread::sleep(Duration::from_secs(1));
1765 match try_rebuild_channel(
1766 &mut conn,
1767 &mut root,
1768 &rsettings,
1769 &mut cap_x,
1770 &mut cap_y,
1771 &mut cap_w,
1772 &mut cap_h,
1773 &mut root_w,
1774 &mut root_h,
1775 POOL_N,
1776 &pool,
1777 &mut surfaces,
1778 &controls.stop,
1779 ) {
1780 Ok(()) => {
1781 damage = RootDamage::create(&conn, root);
1784 shared = None;
1785 rewatch = true;
1786 recovered = true;
1787 break;
1788 }
1789 Err(re) => {
1790 eprintln!("[X11] reconnect attempt failed: {re}");
1791 }
1792 }
1793 }
1794 if !recovered {
1795 return Err(format!("capture could not recover: {e}"));
1796 }
1797 grab_failures = 0;
1798 geometry_check = 0;
1799 reconnect_streak += 1;
1800 if reconnect_streak >= 4 {
1801 return Err(
1802 "capture recovered its channel but the region never grabbed again"
1803 .to_string(),
1804 );
1805 }
1806 }
1807 continue;
1808 }
1809 grab_failures = 0;
1810 reconnect_streak = 0;
1811 let surface = &mut surfaces[idx];
1812
1813 let frame_w = i32::from(cap_w);
1814 let frame_h = i32::from(cap_h);
1815 let stride = surface.stride;
1816 let buf = surface.as_mut_slice();
1817 let mut overlays = Vec::new();
1818 let mut cursor_serial = None;
1819
1820 if controls.capture_cursor.load(Ordering::Relaxed)
1821 && let Some(c) = conn
1822 .xfixes_get_cursor_image()
1823 .ok()
1824 .and_then(|c| c.reply().ok())
1825 && c.width > 0
1826 && c.height > 0
1827 {
1828 let (img_x, img_y) = cursor_image_origin(
1833 c.x,
1834 c.y,
1835 c.xhot,
1836 c.yhot,
1837 i32::from(cap_x),
1838 i32::from(cap_y),
1839 );
1840 cursor_serial = Some(c.cursor_serial);
1841 overlay_cursor(
1842 buf,
1843 stride,
1844 frame_w,
1845 frame_h,
1846 i32::from(c.width),
1847 i32::from(c.height),
1848 &c.cursor_image,
1849 img_x,
1850 img_y,
1851 );
1852 if let Some(grab) = shared.as_mut() {
1853 grab.overlaid(
1854 idx,
1855 img_x + i32::from(cap_x),
1856 img_y + i32::from(cap_y),
1857 i32::from(c.width),
1858 i32::from(c.height),
1859 );
1860 }
1861 overlays.push(smithay::utils::Rectangle::new(
1862 (img_x, img_y).into(),
1863 (i32::from(c.width), i32::from(c.height)).into(),
1864 ));
1865 }
1866
1867 if watermark.is_active() {
1868 watermark.update_position(frame_w, frame_h, settings.watermark_location_enum);
1869 watermark.blend_bgra(buf, stride, frame_w, frame_h);
1870 let drawn = watermark.damage_rect(frame_w, frame_h);
1871 overlays.extend(drawn);
1872 if let (Some(grab), Some(r)) = (shared.as_mut(), drawn) {
1873 grab.overlaid(
1874 idx,
1875 r.loc.x + i32::from(cap_x),
1876 r.loc.y + i32::from(cap_y),
1877 r.size.w,
1878 r.size.h,
1879 );
1880 }
1881 }
1882
1883 if overlays != last_overlays || cursor_serial != last_cursor_serial {
1886 changed.extend(last_overlays.iter().copied());
1887 changed.extend(overlays.iter().copied());
1888 last_overlays = overlays;
1889 last_cursor_serial = cursor_serial;
1890 }
1891 let published = pool.publish(
1892 RawFrame {
1893 idx,
1894 ptr: surface.addr,
1895 len: surface.size,
1896 width: cap_w,
1897 height: cap_h,
1898 stride,
1899 generation: pool.generation(),
1900 captured_ns: crate::wayland::host::now_ns(),
1901 changed,
1902 complete: shared.is_some(),
1903 planes: use_planes.then(|| &planes_pool[idx] as *const Nv12Planes),
1904 },
1905 &controls.stop,
1906 );
1907 if !published {
1908 break;
1909 }
1910 }
1911 Ok(())
1912 })();
1913
1914 pool.shutdown();
1915 let _ = encode_thread.join();
1916 if let Some(grab) = shared.take() {
1917 grab.fb.close(&conn);
1918 }
1919 for s in surfaces.iter_mut() {
1920 s.destroy(&conn);
1921 }
1922 match result {
1923 Err(e) => Err(e),
1925 Ok(()) if encode_panicked.load(Ordering::Acquire) => Err("encode thread panicked".into()),
1926 Ok(()) => Ok(()),
1927 }
1928}
1929
1930#[cfg(test)]
1933pub(crate) mod gpu_test_support {
1934 use std::time::Duration;
1935
1936 use crate::CaptureSettings;
1937 use crate::encoders::codec::Codec;
1938 use crate::webcam::decode::{Decoder, VideoDecoder};
1939
1940 pub(crate) fn settings(codec: Codec) -> CaptureSettings {
1943 CaptureSettings {
1944 codec,
1945 target_fps: 60.0,
1946 video_crf: 25,
1947 video_streaming_mode: true,
1948 encode_node_index: crate::auto_render_node("true")
1949 .and_then(|node| crate::render_node_index(&node))
1950 .unwrap_or(0),
1951 ..Default::default()
1952 }
1953 }
1954
1955 pub(crate) fn paint_root(rgb: (u8, u8, u8)) -> bool {
1962 let _ = std::process::Command::new("xset")
1963 .args(["s", "off", "s", "noblank"])
1964 .output();
1965 let _ = std::process::Command::new("xset")
1966 .arg("s")
1967 .arg("reset")
1968 .output();
1969 let spec = format!("#{:02x}{:02x}{:02x}", rgb.0, rgb.1, rgb.2);
1970 let out = std::process::Command::new("xsetroot")
1971 .args(["-solid", &spec])
1972 .output();
1973 if !out.map(|o| o.status.success()).unwrap_or(false) {
1974 return false;
1975 }
1976 std::thread::sleep(Duration::from_millis(120));
1977 true
1978 }
1979
1980 pub(crate) fn painted_ycbcr(rgb: (u8, u8, u8)) -> [f64; 3] {
1983 use crate::encoders::chroma_siting::{BT709, ycbcr};
1984 ycbcr([rgb.0, rgb.1, rgb.2].map(f64::from), BT709)
1985 }
1986
1987 pub(crate) fn decoded_mean(dec: &mut VideoDecoder, payload: &[u8]) -> [f64; 3] {
1989 assert!(
1990 dec.decode(payload).expect("decode"),
1991 "no picture from this access unit"
1992 );
1993 let v = dec.frame().expect("decoded frame");
1994 let mut acc = [0f64; 3];
1995 let mut n = 0f64;
1996 for y in 0..v.height {
1997 for x in 0..v.width {
1998 acc[0] += f64::from(v.y[y * v.y_stride + x]);
1999 acc[1] += f64::from(v.u[(y / 2) * v.uv_stride + x / 2]);
2000 acc[2] += f64::from(v.v[(y / 2) * v.uv_stride + x / 2]);
2001 n += 1.0;
2002 }
2003 }
2004 [acc[0] / n, acc[1] / n, acc[2] / n]
2005 }
2006
2007 pub(crate) type Capture = fn(
2009 CaptureSettings,
2010 std::sync::Arc<super::Controls>,
2011 std::sync::mpsc::Sender<std::thread::ThreadId>,
2012 &mut dyn FnMut(Vec<crate::encoders::software::EncodedStripe>),
2013 ) -> Option<Result<(), String>>;
2014
2015 pub(crate) fn capped_crf_codes_again_what_it_coded_bound(
2021 capture: Capture,
2022 paint_over: bool,
2023 ) -> Option<[(f64, usize, usize, usize); 2]> {
2024 use crate::encoders::codec::{FRAME_KEY, VIDEO_HEADER_LEN, parse_video_type};
2025 use std::sync::atomic::Ordering;
2026 use std::time::Instant;
2027 use x11rb::connection::Connection;
2028 use x11rb::protocol::xproto::{ConnectionExt as _, CreateGCAux, ImageFormat};
2029 const W: usize = 640;
2030 const H: usize = 360;
2031 const SCROLL: usize = 120;
2032 if !paint_root((0x12, 0x12, 0x12)) {
2033 println!("no X root this host can paint (xsetroot, $DISPLAY); nothing to capture");
2034 return None;
2035 }
2036 let (conn, screen) = x11rb::connect(None).expect("connect");
2037 let root = conn.setup().roots[screen].root;
2038 let gc = conn.generate_id().expect("id");
2039 conn.create_gc(gc, root, &CreateGCAux::new()).expect("gc");
2040 let paint = |t: usize| {
2041 let page = crate::encoders::scrolled_text(W, H, t);
2042 for (band, rows) in page.chunks(W * 4 * 64).enumerate() {
2043 conn.put_image(
2044 ImageFormat::Z_PIXMAP,
2045 root,
2046 gc,
2047 W as u16,
2048 (rows.len() / (W * 4)) as u16,
2049 0,
2050 (band * 64) as i16,
2051 0,
2052 24,
2053 rows,
2054 )
2055 .expect("put")
2056 .check()
2057 .expect("put done");
2058 }
2059 };
2060 let run = |capped: bool| -> Option<(f64, usize, usize, usize)> {
2061 paint(0);
2062 let mut s = settings(Codec::H264);
2063 s.width = W as i32;
2064 s.height = H as i32;
2065 s.video_streaming_mode = false;
2066 s.use_paint_over_quality = paint_over;
2067 s.video_paintover_crf = 18;
2068 s.video_bitrate_kbps = if capped { 300 } else { 8000 };
2069 s.video_vbv_multiplier = if capped {
2070 crate::encoders::CAPPED_VBV_FRAMES
2071 } else {
2072 0.0
2073 };
2074 let vbv = s.video_vbv_multiplier;
2075 let controls = std::sync::Arc::new(super::Controls::new(&s));
2076 let (tx, rx) = std::sync::mpsc::channel::<(Instant, Vec<u8>)>();
2077 let thread = {
2078 let controls = controls.clone();
2079 std::thread::spawn(move || {
2080 let (tid, _rx) = std::sync::mpsc::channel();
2081 capture(s, controls, tid, &mut |stripes| {
2082 for st in stripes {
2083 let _ = tx.send((Instant::now(), st.data.to_vec()));
2084 }
2085 })
2086 })
2087 };
2088 let mut frames = Vec::new();
2090 let end = Instant::now() + Duration::from_secs(15);
2091 while frames.is_empty() && !thread.is_finished() && Instant::now() < end {
2092 paint(0);
2093 std::thread::sleep(Duration::from_millis(50));
2094 frames.extend(rx.try_iter());
2095 }
2096 if thread.is_finished() {
2097 return None;
2098 }
2099 for t in 1..=SCROLL {
2100 paint(t);
2101 std::thread::sleep(Duration::from_millis(16));
2102 }
2103 std::thread::sleep(Duration::from_millis(100));
2104 let still = Instant::now();
2105 if capped {
2106 controls.bitrate_kbps.store(100_000, Ordering::Relaxed);
2107 controls
2108 .vbv_mult_milli
2109 .store((vbv * 1000.0).round() as i32, Ordering::Relaxed);
2110 controls.rate_dirty.store(true, Ordering::Release);
2111 }
2112 std::thread::sleep(Duration::from_secs(3));
2113 controls.stop.store(true, Ordering::Relaxed);
2114 let ended = thread.join().expect("capture thread");
2115 assert!(matches!(ended, Some(Ok(()))), "{ended:?}");
2116 frames.extend(rx.try_iter());
2117 let mut dec = VideoDecoder::new(crate::webcam::decode::Codec::H264).expect("decoder");
2118 let (mut sent, mut bytes, mut keys) = (0, 0, 0);
2119 for (i, (at, data)) in frames.iter().enumerate() {
2120 if *at > still {
2121 sent += 1;
2122 bytes += data.len();
2123 }
2124 keys += usize::from(
2125 i > 0 && parse_video_type(data[1]).map(|(_, k)| k) == Some(FRAME_KEY),
2126 );
2127 dec.decode(&data[VIDEO_HEADER_LEN..]).expect("decode");
2128 }
2129 let shown = dec.frame().expect("picture");
2130 let page = crate::encoders::scrolled_text(W, H, SCROLL);
2131 let mut mse = 0.0;
2132 for y in 0..H {
2133 for x in 0..W {
2134 let want = 16.0 + f64::from(page[(y * W + x) * 4]) * 219.0 / 255.0;
2135 let d = f64::from(shown.y[y * shown.y_stride + x]) - want;
2136 mse += d * d;
2137 }
2138 }
2139 let psnr = 10.0 * (255.0f64 * 255.0 / (mse / (W * H) as f64)).log10();
2140 Some((psnr, sent, bytes, keys))
2141 };
2142 let plain = run(false)?;
2143 Some([plain, run(true).expect("the capped capture")])
2144 }
2145}
2146
2147#[cfg(test)]
2148mod pool_tests {
2149 use super::*;
2150
2151 fn dummy(idx: usize) -> RawFrame {
2152 RawFrame {
2153 idx,
2154 ptr: std::ptr::null_mut(),
2155 len: 0,
2156 width: 0,
2157 height: 0,
2158 stride: 0,
2159 generation: 0,
2160 captured_ns: 0,
2161 changed: Vec::new(),
2162 complete: false,
2163 planes: None,
2164 }
2165 }
2166
2167 #[test]
2170 fn roundtrip_then_recycle_returns_all_surfaces() {
2171 let p = FramePool::new(3);
2172 let stop = AtomicBool::new(false);
2173 let a = p.acquire(&stop).unwrap();
2174 assert!(p.publish(dummy(a), &stop));
2175 let f = p.take(|| {}).unwrap();
2176 assert_eq!(f.idx, a);
2177 p.recycle(f.idx);
2178 let (x, y, z) = (
2179 p.acquire(&stop).unwrap(),
2180 p.acquire(&stop).unwrap(),
2181 p.acquire(&stop).unwrap(),
2182 );
2183 assert!(x != y && y != z && x != z);
2184 }
2185
2186 #[test]
2189 fn acquire_returns_none_when_exhausted_and_stopped() {
2190 let p = FramePool::new(2);
2191 let stop = AtomicBool::new(false);
2192 let _a = p.acquire(&stop).unwrap();
2193 let _b = p.acquire(&stop).unwrap();
2194 stop.store(true, Ordering::Relaxed);
2195 assert!(p.acquire(&stop).is_none());
2196 }
2197
2198 #[test]
2201 fn publish_returns_false_when_slot_full_and_stopped() {
2202 let p = FramePool::new(3);
2203 let stop = AtomicBool::new(false);
2204 let a = p.acquire(&stop).unwrap();
2205 assert!(p.publish(dummy(a), &stop));
2206 let b = p.acquire(&stop).unwrap();
2207 stop.store(true, Ordering::Relaxed);
2208 assert!(!p.publish(dummy(b), &stop));
2209 }
2210
2211 #[test]
2215 fn drain_for_resize_waits_until_all_free() {
2216 let p = Arc::new(FramePool::new(3));
2217 let stop = AtomicBool::new(false);
2218 let held = [
2219 p.acquire(&stop).unwrap(),
2220 p.acquire(&stop).unwrap(),
2221 p.acquire(&stop).unwrap(),
2222 ];
2223 let p2 = p.clone();
2224 let t = thread::spawn(move || {
2225 thread::sleep(Duration::from_millis(30));
2226 for idx in held {
2227 p2.recycle(idx);
2228 }
2229 });
2230 assert!(p.drain_for_resize(3, &stop));
2231 t.join().unwrap();
2232 assert!(p.acquire(&stop).is_some());
2233 }
2234
2235 #[test]
2238 fn a_held_surface_stays_out_until_settled() {
2239 let p = FramePool::new(3);
2240 let stop = AtomicBool::new(false);
2241 let (a, b, c) = (
2242 p.acquire(&stop).unwrap(),
2243 p.acquire(&stop).unwrap(),
2244 p.acquire(&stop).unwrap(),
2245 );
2246 p.settle(&[b, c], Some(a));
2247 let (x, y) = (p.acquire(&stop).unwrap(), p.acquire(&stop).unwrap());
2248 assert!(![x, y].contains(&a));
2249 stop.store(true, Ordering::Relaxed);
2250 assert!(p.acquire(&stop).is_none(), "the held surface is not free");
2251 stop.store(false, Ordering::Relaxed);
2252 p.settle(&[a, x], Some(y));
2253 assert!(p.acquire(&stop).is_some() && p.acquire(&stop).is_some());
2254 p.recycle(a);
2255 stop.store(true, Ordering::Relaxed);
2256 assert!(
2257 p.acquire(&stop).is_some() && p.acquire(&stop).is_none(),
2258 "a recycle by the capture leaves the held surface held"
2259 );
2260 }
2261
2262 #[test]
2266 fn drain_for_resize_releases_the_held_surface() {
2267 let p = Arc::new(FramePool::new(3));
2268 let stop = AtomicBool::new(false);
2269 let a = p.acquire(&stop).unwrap();
2270 p.settle(&[], Some(a));
2271 let p2 = p.clone();
2272 let drain = thread::spawn(move || {
2273 let drained = p2.drain_for_resize(3, &AtomicBool::new(false));
2274 let b = p2.acquire(&AtomicBool::new(false)).unwrap();
2275 assert!(p2.publish(dummy(b), &AtomicBool::new(false)));
2276 drained
2277 });
2278 let mut released = 0;
2279 let f = p.take(|| released += 1).unwrap();
2280 assert!(drain.join().unwrap());
2281 assert_eq!(released, 1);
2282 p.recycle(f.idx);
2283 }
2284
2285 #[test]
2288 fn drain_for_resize_aborts_on_stop() {
2289 let p = FramePool::new(3);
2290 let stop = AtomicBool::new(false);
2291 let _held = p.acquire(&stop).unwrap();
2292 stop.store(true, Ordering::Relaxed);
2293 assert!(!p.drain_for_resize(3, &stop));
2294 }
2295
2296 #[test]
2301 fn resize_flap_reclaim_changes_generation_at_identical_dims() {
2302 let p = FramePool::new(3);
2303 let stop = AtomicBool::new(false);
2304 let a = p.acquire(&stop).unwrap();
2305 assert!(p.publish(
2306 RawFrame {
2307 generation: p.generation(),
2308 width: 1920,
2309 height: 1080,
2310 ..dummy(a)
2311 },
2312 &stop
2313 ));
2314 let f = p.take(|| {}).unwrap();
2315 let (last_gen, last_dims) = (f.generation, (f.width, f.height));
2316 p.recycle(f.idx);
2317 assert!(p.drain_for_resize(3, &stop));
2318 p.bump_generation();
2319 let b = p.acquire(&stop).unwrap();
2320 assert!(p.publish(
2321 RawFrame {
2322 generation: p.generation(),
2323 width: 2560,
2324 height: 1600,
2325 ..dummy(b)
2326 },
2327 &stop
2328 ));
2329 assert!(p.drain_for_resize(3, &stop));
2330 p.bump_generation();
2331 let c = p.acquire(&stop).unwrap();
2332 assert!(p.publish(
2333 RawFrame {
2334 generation: p.generation(),
2335 width: 1920,
2336 height: 1080,
2337 ..dummy(c)
2338 },
2339 &stop
2340 ));
2341 let g = p.take(|| {}).unwrap();
2342 assert_eq!(
2343 (g.width, g.height),
2344 last_dims,
2345 "flap lands on identical dimensions"
2346 );
2347 assert_eq!(g.generation, 2);
2348 assert_ne!(
2349 g.generation, last_gen,
2350 "generation is the only rebuild signal"
2351 );
2352 }
2353
2354 #[test]
2357 fn generation_bumps_on_recreate_and_rides_published_frames() {
2358 let p = FramePool::new(3);
2359 let stop = AtomicBool::new(false);
2360 assert_eq!(p.generation(), 0);
2361 let a = p.acquire(&stop).unwrap();
2362 assert!(p.publish(
2363 RawFrame {
2364 generation: p.generation(),
2365 ..dummy(a)
2366 },
2367 &stop
2368 ));
2369 let f = p.take(|| {}).unwrap();
2370 assert_eq!(f.generation, 0);
2371 p.recycle(f.idx);
2372 p.bump_generation();
2373 let b = p.acquire(&stop).unwrap();
2374 assert!(p.publish(
2375 RawFrame {
2376 generation: p.generation(),
2377 ..dummy(b)
2378 },
2379 &stop
2380 ));
2381 assert_eq!(p.take(|| {}).unwrap().generation, 1);
2382 }
2383}
2384
2385#[cfg(test)]
2386mod cursor_tests {
2387 use super::*;
2388
2389 #[test]
2393 fn origin_subtracts_hotspot_and_capture_offset() {
2394 assert_eq!(cursor_image_origin(100, 80, 4, 6, 0, 0), (96, 74));
2395 assert_eq!(cursor_image_origin(100, 80, 4, 6, 10, 20), (86, 54));
2396 assert_eq!(cursor_image_origin(1, 1, 8, 8, 0, 0), (-7, -7));
2397 }
2398
2399 #[test]
2403 fn overlay_blits_at_hotspot_offset_origin() {
2404 let stride = 8 * 4;
2405 let mut frame = vec![0u8; stride * 8];
2406 let pixels = [0xFFFF_FFFFu32; 4];
2407 let (ox, oy) = cursor_image_origin(4, 4, 1, 1, 0, 0);
2408 overlay_cursor(&mut frame, stride, 8, 8, 2, 2, &pixels, ox, oy);
2409 let px = |x: usize, y: usize| frame[y * stride + x * 4];
2410 assert_eq!(px(3, 3), 255);
2411 assert_eq!(px(4, 4), 255);
2412 assert_eq!(px(2, 2), 0);
2413 assert_eq!(px(5, 5), 0, "no pixel at the un-offset (hotspot) corner");
2414 }
2415
2416 #[test]
2417 fn offset_clamps_into_root() {
2418 assert_eq!(clamp_offset(100, 1920), 100);
2419 assert_eq!(clamp_offset(-50, 1920), 0);
2420 assert_eq!(clamp_offset(100000, 1920), 1919);
2422 assert_eq!(clamp_offset(40000, u16::MAX), 32766);
2424 }
2425
2426 #[test]
2427 fn resolve_dims_survives_past_end_offset() {
2428 let s = CaptureSettings {
2429 capture_x: 100000,
2430 capture_y: 100000,
2431 width: 1920,
2432 height: 1080,
2433 codec: crate::encoders::Codec::H264,
2434 ..Default::default()
2435 };
2436 let (w, h) = resolve_dims(1920, 1080, &s);
2437 assert!(
2438 w >= 2 && h >= 2,
2439 "a past-end offset now degrades to a small grab, not death"
2440 );
2441 assert_eq!(w % 2, 0);
2442 assert_eq!(h % 2, 0);
2443 }
2444
2445 #[test]
2449 fn overlay_blends_premultiplied_pixels() {
2450 let stride = 4 * 4;
2451 let mut frame = vec![0u8; stride * 4];
2452 let pixels = [0x8000_8040u32; 4];
2454 overlay_cursor(&mut frame, stride, 4, 4, 2, 2, &pixels, 0, 0);
2455 assert_eq!(frame[0], 64, "blue channel: dst = 64 over black");
2456 assert_eq!(
2457 frame[1], 128,
2458 "premultiplied half-alpha stays 128, not darkened to 64"
2459 );
2460 }
2461
2462 #[test]
2465 fn overlay_clips_negative_origin_at_frame_edge() {
2466 let stride = 4 * 4;
2467 let mut frame = vec![0u8; stride * 4];
2468 let pixels = [0xFFFF_FFFFu32; 4];
2469 let (ox, oy) = cursor_image_origin(0, 0, 1, 1, 0, 0);
2470 overlay_cursor(&mut frame, stride, 4, 4, 2, 2, &pixels, ox, oy);
2471 assert_eq!(frame[0], 255);
2472 assert!(
2473 frame[4..].iter().all(|&b| b == 0),
2474 "no writes outside (0,0)"
2475 );
2476 }
2477}
2478
2479#[cfg(test)]
2480mod damage_tests {
2481 use super::*;
2486 use crate::pipeline::Damage as Changed;
2487 use x11rb::protocol::xproto::CreateGCAux;
2488
2489 const PERIOD: Duration = Duration::from_millis(400);
2490
2491 fn fill(color: u32, x: i16, y: i16, width: u16, height: u16) {
2492 Painter::connect().fill(color, x, y, width, height);
2493 }
2494
2495 struct Painter {
2498 conn: RustConnection,
2499 root: u32,
2500 }
2501
2502 impl Painter {
2503 fn connect() -> Self {
2504 let (conn, screen) = x11rb::connect(None).expect("connect");
2505 let root = conn.setup().roots[screen].root;
2506 Self { conn, root }
2507 }
2508
2509 fn fill(&self, color: u32, x: i16, y: i16, width: u16, height: u16) {
2512 let gc = self.conn.generate_id().expect("id");
2513 self.conn
2514 .create_gc(gc, self.root, &CreateGCAux::new().foreground(color))
2515 .expect("gc");
2516 self.conn
2517 .poly_fill_rectangle(
2518 self.root,
2519 gc,
2520 &[Rectangle {
2521 x,
2522 y,
2523 width,
2524 height,
2525 }],
2526 )
2527 .expect("fill");
2528 self.conn.free_gc(gc).expect("free");
2529 self.conn
2530 .get_input_focus()
2531 .expect("sync")
2532 .reply()
2533 .expect("sync");
2534 }
2535 }
2536
2537 #[test]
2539 fn the_root_damage_reads_as_the_captured_share() {
2540 let caret = [Rectangle {
2541 x: 110,
2542 y: 60,
2543 width: 2,
2544 height: 20,
2545 }];
2546 assert_eq!(
2547 captured_damage(&caret, 100, 50, 200, 100),
2548 Changed::Area(0.002)
2549 );
2550 assert_eq!(
2551 captured_damage(&caret, 120, 50, 200, 100),
2552 Changed::None,
2553 "left of the captured area"
2554 );
2555 let wide = [Rectangle {
2556 x: 0,
2557 y: 0,
2558 width: 1000,
2559 height: 1000,
2560 }];
2561 assert_eq!(
2562 captured_damage(&wide, 100, 50, 200, 100),
2563 Changed::Area(1.0)
2564 );
2565 assert_eq!(captured_damage(&[], 0, 0, 200, 100), Changed::None);
2566 }
2567
2568 #[test]
2571 #[ignore]
2572 fn x11_damage_says_where_the_root_changed() {
2573 let (conn, screen) = x11rb::connect(None).expect("connect");
2574 let root = conn.setup().roots[screen].root;
2575 let (w, h) = (
2576 i32::from(conn.setup().roots[screen].width_in_pixels),
2577 i32::from(conn.setup().roots[screen].height_in_pixels),
2578 );
2579 let damage = RootDamage::create(&conn, root).expect("the server carries DAMAGE");
2580 damage.clear(&conn);
2581 assert!(
2582 damage.take(&conn).is_some_and(|rects| rects.is_empty()),
2583 "nothing reported yet"
2584 );
2585 let mut pace = FramePace::windowed();
2586 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2587 fill(0x00ff_ffff, 300, 200, 2, 20);
2588 assert_eq!(
2589 wait_for_frame(&conn, Some(&damage), &pace, PERIOD),
2590 TickTrigger::Damage
2591 );
2592 let asked = Instant::now();
2593 let rects = damage.take(&conn).expect("the server says where");
2594 let took = asked.elapsed();
2595 let caret = captured_damage(&rects, 0, 0, w, h);
2596 assert_eq!(
2597 caret,
2598 Changed::Area((40.0 / f64::from(w * h)) as f32),
2599 "{rects:?}"
2600 );
2601 assert_eq!(
2602 captured_damage(&rects, 400, 0, w - 400, h),
2603 Changed::None,
2604 "right of the caret"
2605 );
2606 println!("a 2x20 caret on a {w}x{h} root reads as {caret:?}, read in {took:?}");
2607 }
2608
2609 #[test]
2612 #[ignore]
2613 fn x11_damage_a_change_after_the_wait_is_the_ticks() {
2614 let (conn, screen) = x11rb::connect(None).expect("connect");
2615 let root = conn.setup().roots[screen].root;
2616 let damage = RootDamage::create(&conn, root).expect("the server carries DAMAGE");
2617 let mut pace = FramePace::windowed();
2618 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2619 damage.clear(&conn);
2620 let trigger = wait_for_frame(&conn, Some(&damage), &pace, PERIOD);
2621 assert_eq!(trigger, TickTrigger::Timer, "a quiet screen is the timer's");
2622 fill(0x0040_8020, 10, 10, 8, 8);
2623 let (dirty, rects) = take_tick(&damage, &conn, trigger);
2624 assert!(dirty, "the late change is the tick's: {rects:?}");
2625 assert!(
2626 rects.is_some_and(|rects| !rects.is_empty()),
2627 "and says where"
2628 );
2629 let (dirty, rects) = take_tick(&damage, &conn, TickTrigger::Timer);
2630 assert!(!dirty, "taken once: {rects:?}");
2631 }
2632
2633 #[test]
2636 #[ignore]
2637 fn x11_damage_publishes_a_change_right_after_a_frame() {
2638 let (conn, screen) = x11rb::connect(None).expect("connect");
2639 let root = conn.setup().roots[screen].root;
2640 let damage = RootDamage::create(&conn, root).expect("the server carries DAMAGE");
2641 let mut pace = FramePace::windowed();
2642 pace.record_tick(TickTrigger::Timer, PERIOD, Instant::now(), true);
2643 damage.clear(&conn);
2644 let brush = Painter::connect();
2645 let painter = thread::spawn(move || {
2646 thread::sleep(PERIOD.mul_f64(0.1));
2647 brush.fill(0x0060_2040, 0, 0, 64, 64);
2648 });
2649 let start = Instant::now();
2650 let pulled = wait_for_frame(&conn, Some(&damage), &pace, PERIOD);
2651 let waited = start.elapsed();
2652 painter.join().expect("painter");
2653 assert_eq!(pulled, TickTrigger::Damage);
2654 assert!(
2655 waited < PERIOD.mul_f64(0.35),
2656 "a change a tenth of a period after a frame was published after {waited:?}"
2657 );
2658 }
2659
2660 #[test]
2663 #[ignore]
2664 fn x11_damage_publishes_a_change_before_the_next_tick() {
2665 let (conn, screen) = x11rb::connect(None).expect("connect");
2666 let root = conn.setup().roots[screen].root;
2667 let damage = RootDamage::create(&conn, root).expect("the server carries DAMAGE");
2668 let mut pace = FramePace::windowed();
2669
2670 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2671 damage.clear(&conn);
2672 let start = Instant::now();
2673 let quiet = wait_for_frame(&conn, Some(&damage), &pace, PERIOD);
2674 let waited = start.elapsed();
2675 assert_eq!(
2676 quiet,
2677 TickTrigger::Timer,
2678 "a quiet screen is the timer's to pace"
2679 );
2680 assert!(
2681 waited >= PERIOD.mul_f64(0.9),
2682 "the timer's own tick came after {waited:?}"
2683 );
2684
2685 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2686 damage.clear(&conn);
2687 let brush = Painter::connect();
2688 let painter = thread::spawn(move || {
2689 thread::sleep(PERIOD.mul_f64(0.6));
2690 brush.fill(0x0020_4060, 0, 0, 64, 64);
2691 });
2692 let start = Instant::now();
2693 let pulled = wait_for_frame(&conn, Some(&damage), &pace, PERIOD);
2694 let waited = start.elapsed();
2695 painter.join().expect("painter");
2696 assert_eq!(
2697 pulled,
2698 TickTrigger::Damage,
2699 "the repaint is what asked for the frame"
2700 );
2701 assert!(
2702 waited < PERIOD.mul_f64(0.9),
2703 "the repaint was published after {waited:?}"
2704 );
2705 println!(
2706 "repaint at 0.6 of a period published after {:.0}ms",
2707 waited.as_secs_f64() * 1000.0
2708 );
2709 }
2710
2711 #[test]
2714 #[ignore]
2715 fn x11_framebuffer_publishes_a_change_before_the_next_tick() {
2716 let (conn, screen) = x11rb::connect(None).expect("connect");
2717 let root = conn.setup().roots[screen].root;
2718 let Opened::Watch(fb) = SharedFramebuffer::open(&conn, root) else {
2719 panic!("the server shares no framebuffer");
2720 };
2721 let mut pace = FramePace::windowed();
2722 let mut grab = FramebufferGrab::new(fb, 1);
2723 let mut frame = vec![0u8; grab.fb.width as usize * grab.fb.height as usize * 4];
2724 let region = (0, 0, grab.fb.width, grab.fb.height);
2725 assert!(grab.read(0, &mut frame, grab.fb.width as usize * 4, region));
2726
2727 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2728 let start = Instant::now();
2729 let quiet = wait_for_framebuffer(&grab.fb, &pace, PERIOD);
2730 assert_eq!(
2731 quiet,
2732 TickTrigger::Timer,
2733 "a quiet screen is the timer's to pace"
2734 );
2735 assert!(start.elapsed() >= PERIOD.mul_f64(0.9));
2736
2737 pace.ticked(TickTrigger::Timer, PERIOD, Instant::now(), false);
2738 let brush = Painter::connect();
2739 let painter = thread::spawn(move || {
2740 thread::sleep(PERIOD.mul_f64(0.6));
2741 brush.fill(0x0020_4060, 0, 0, 64, 64);
2742 });
2743 let start = Instant::now();
2744 let pulled = wait_for_framebuffer(&grab.fb, &pace, PERIOD);
2745 let waited = start.elapsed();
2746 painter.join().expect("painter");
2747 assert_eq!(
2748 pulled,
2749 TickTrigger::Damage,
2750 "the repaint is what asked for the frame"
2751 );
2752 assert!(
2753 waited < PERIOD.mul_f64(0.9),
2754 "the repaint was published after {waited:?}"
2755 );
2756 assert!(grab.read(0, &mut frame, grab.fb.width as usize * 4, region));
2757 assert_eq!(
2758 &frame[..4],
2759 &0x0020_4060u32.to_le_bytes(),
2760 "and the frame read after it holds it"
2761 );
2762 println!(
2763 "repaint at 0.6 of a period published after {:.2}ms",
2764 waited.as_secs_f64() * 1000.0
2765 );
2766 grab.fb.close(&conn);
2767 }
2768
2769 #[test]
2772 #[ignore]
2773 fn x11_framebuffer_capture_reads_the_screen_in_place() {
2774 use crate::encoders::codec::{Codec, VIDEO_HEADER_LEN};
2775 use crate::webcam::decode::{Codec as DecCodec, VideoDecoder};
2776 use crate::x11::gpu_test_support::{decoded_mean, painted_ycbcr};
2777 let (conn, screen) = x11rb::connect(None).expect("connect");
2778 let (width, height) = (
2779 conn.setup().roots[screen].width_in_pixels,
2780 conn.setup().roots[screen].height_in_pixels,
2781 );
2782 let settings = CaptureSettings {
2783 codec: Codec::H264,
2784 use_cpu: true,
2785 video_streaming_mode: true,
2786 target_fps: 30.0,
2787 capture_cursor: true,
2788 ..Default::default()
2789 };
2790 let controls = Arc::new(Controls::new(&settings));
2791 let (tx, rx) = std::sync::mpsc::channel::<Vec<u8>>();
2792 let (tid_tx, _tid_rx) = std::sync::mpsc::channel();
2793 let capture = {
2794 let controls = controls.clone();
2795 thread::spawn(move || {
2796 run_capture(settings, controls, tid_tx, move |stripes| {
2797 for s in stripes {
2798 let _ = tx.send(s.data.to_vec());
2799 }
2800 })
2801 })
2802 };
2803 thread::sleep(Duration::from_millis(500));
2804 const PAINTED: (u8, u8, u8) = (0xc0, 0x40, 0x20);
2805 fill(0x00c0_4020, 0, 0, width, height);
2806 thread::sleep(Duration::from_millis(500));
2807 controls.stop.store(true, Ordering::Relaxed);
2808 capture.join().expect("capture").expect("capture ran");
2809 let mut dec = VideoDecoder::new(DecCodec::H264).expect("H.264 decoder");
2810 let mut mean = [0.0; 3];
2811 for pkt in rx.try_iter() {
2812 mean = decoded_mean(&mut dec, &pkt[VIDEO_HEADER_LEN..]);
2813 }
2814 let info = controls.report.info();
2815 assert_eq!(info.capture, "Framebuffer", "{}", info.capture_reason);
2816 let want = painted_ycbcr(PAINTED);
2817 for (got, want) in mean.iter().zip(want) {
2818 assert!(
2819 (got - want).abs() < 2.0,
2820 "decoded {mean:?}, painted {want:?}"
2821 );
2822 }
2823 }
2824
2825 #[test]
2829 #[ignore]
2830 fn x11_framebuffer_publishes_a_cursor_that_changes_its_image() {
2831 use crate::encoders::codec::Codec;
2832 use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
2833 const XC_LEFT_PTR: u16 = 68;
2834 let (conn, screen) = x11rb::connect(None).expect("connect");
2835 let root = conn.setup().roots[screen].root;
2836 let (width, height) = (
2837 conn.setup().roots[screen].width_in_pixels,
2838 conn.setup().roots[screen].height_in_pixels,
2839 );
2840 let font = conn.generate_id().expect("id");
2841 conn.open_font(font, b"cursor").expect("cursor font");
2842 let arrow = |red: u16| {
2843 let cursor = conn.generate_id().expect("id");
2844 conn.create_glyph_cursor(
2845 cursor,
2846 font,
2847 font,
2848 XC_LEFT_PTR,
2849 XC_LEFT_PTR + 1,
2850 red,
2851 0,
2852 0,
2853 0xffff,
2854 0xffff,
2855 0xffff,
2856 )
2857 .expect("cursor");
2858 cursor
2859 };
2860 let shapes = [arrow(0xffff), arrow(0)];
2861 let settings = CaptureSettings {
2862 codec: Codec::H264,
2863 use_cpu: true,
2864 target_fps: 30.0,
2865 width: i32::from(width),
2866 height: i32::from(height),
2867 capture_cursor: true,
2868 use_paint_over_quality: false,
2869 ..Default::default()
2870 };
2871 let controls = Arc::new(Controls::new(&settings));
2872 let (tx, rx) = std::sync::mpsc::channel::<()>();
2873 let (tid_tx, _tid_rx) = std::sync::mpsc::channel();
2874 let capture = {
2875 let controls = controls.clone();
2876 thread::spawn(move || {
2877 run_capture(settings, controls, tid_tx, move |stripes| {
2878 if !stripes.is_empty() {
2879 let _ = tx.send(());
2880 }
2881 })
2882 })
2883 };
2884 thread::sleep(Duration::from_millis(800));
2885 while rx.try_recv().is_ok() {}
2886 let mut sent = 0;
2887 for &cursor in shapes.iter().cycle().take(4) {
2888 conn.change_window_attributes(root, &ChangeWindowAttributesAux::new().cursor(cursor))
2889 .expect("cursor")
2890 .check()
2891 .expect("set cursor");
2892 thread::sleep(Duration::from_millis(300));
2893 sent += usize::from(rx.try_iter().count() > 0);
2894 }
2895 controls.stop.store(true, Ordering::Relaxed);
2896 capture.join().expect("capture").expect("capture ran");
2897 let info = controls.report.info();
2898 assert_eq!(info.capture, "Framebuffer", "{}", info.capture_reason);
2899 assert_eq!(sent, 4, "a frame for each change of the cursor's image");
2900 }
2901
2902 fn cpu_seconds(pid: &str) -> f64 {
2904 let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).expect("stat");
2905 let fields: Vec<&str> = stat
2906 .rsplit(')')
2907 .next()
2908 .unwrap()
2909 .split_whitespace()
2910 .collect();
2911 let ticks: f64 = fields[11].parse::<f64>().unwrap() + fields[12].parse::<f64>().unwrap();
2912 ticks / unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as f64
2913 }
2914
2915 #[test]
2921 #[ignore]
2922 fn x11_framebuffer_bench_capture_cost() {
2923 use crate::encoders::codec::Codec;
2924 let xpid = std::env::var("PF_BENCH_XPID").expect("PF_BENCH_XPID");
2925 let boxed: Option<u16> = std::env::var("PF_BENCH_BOX")
2926 .ok()
2927 .and_then(|v| v.parse().ok());
2928 let (conn, screen) = x11rb::connect(None).expect("connect");
2929 let (width, height) = (
2930 conn.setup().roots[screen].width_in_pixels,
2931 conn.setup().roots[screen].height_in_pixels,
2932 );
2933 let settings = CaptureSettings {
2934 codec: Codec::H264,
2935 use_cpu: true,
2936 target_fps: 60.0,
2937 width: i32::from(width),
2938 height: i32::from(height),
2939 capture_cursor: true,
2940 ..Default::default()
2941 };
2942 let controls = Arc::new(Controls::new(&settings));
2943 let frames = Arc::new(AtomicU64::new(0));
2944 let (tid_tx, _tid_rx) = std::sync::mpsc::channel();
2945 let capture = {
2946 let (controls, frames) = (controls.clone(), frames.clone());
2947 thread::spawn(move || {
2948 run_capture(settings, controls, tid_tx, move |stripes| {
2949 if !stripes.is_empty() {
2950 frames.fetch_add(1, Ordering::Relaxed);
2951 }
2952 })
2953 })
2954 };
2955 thread::sleep(Duration::from_secs(2));
2956 let painter = Painter::connect();
2957 let (x0, s0, f0) = (
2958 cpu_seconds(&xpid),
2959 cpu_seconds("self"),
2960 frames.load(Ordering::Relaxed),
2961 );
2962 let start = Instant::now();
2963 let mut n = 0u32;
2964 while start.elapsed() < Duration::from_secs(5) {
2965 if let Some(size) = boxed {
2966 let color = 0x0010_2030u32.wrapping_mul(n + 1) & 0x00ff_ffff;
2967 match size {
2968 0 => painter.fill(color, 0, 0, width, height),
2969 _ => painter.fill(color, 64, 64, size, size),
2970 }
2971 }
2972 n += 1;
2973 thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
2974 }
2975 let secs = start.elapsed().as_secs_f64();
2976 let (x1, s1, f1) = (
2977 cpu_seconds(&xpid),
2978 cpu_seconds("self"),
2979 frames.load(Ordering::Relaxed),
2980 );
2981 controls.stop.store(true, Ordering::Relaxed);
2982 capture.join().expect("capture").expect("capture ran");
2983 println!(
2984 "{} {}x{} box {:?}: {:.1} fps painted, {:.1} fps sent, X server {:.3} cores, capture {:.3} cores",
2985 controls.report.info().capture,
2986 width,
2987 height,
2988 boxed,
2989 if boxed.is_some() {
2990 f64::from(n) / secs
2991 } else {
2992 0.0
2993 },
2994 (f1 - f0) as f64 / secs,
2995 (x1 - x0) / secs,
2996 (s1 - s0) / secs
2997 );
2998 }
2999
3000 #[test]
3007 #[ignore]
3008 fn x11_framebuffer_bench_change_latency() {
3009 use crate::encoders::codec::Codec;
3010 let size: u16 = std::env::var("PF_BENCH_BOX")
3011 .ok()
3012 .and_then(|v| v.parse().ok())
3013 .unwrap_or(256);
3014 let (conn, screen) = x11rb::connect(None).expect("connect");
3015 let (width, height) = (
3016 conn.setup().roots[screen].width_in_pixels,
3017 conn.setup().roots[screen].height_in_pixels,
3018 );
3019 let codec = std::env::var("PF_BENCH_CODEC")
3020 .ok()
3021 .and_then(|c| Codec::parse(&c))
3022 .unwrap_or(Codec::H264);
3023 let settings = CaptureSettings {
3024 codec,
3025 use_cpu: std::env::var("PF_BENCH_CPU").map_or(true, |v| v != "0"),
3026 target_fps: 60.0,
3027 width: i32::from(width),
3028 height: i32::from(height),
3029 use_paint_over_quality: false,
3030 ..Default::default()
3031 };
3032 let controls = Arc::new(Controls::new(&settings));
3033 let (tx, rx) = std::sync::mpsc::channel::<Instant>();
3034 let (tid_tx, _tid_rx) = std::sync::mpsc::channel();
3035 let capture = {
3036 let controls = controls.clone();
3037 thread::spawn(move || {
3038 run_capture(settings, controls, tid_tx, move |stripes| {
3039 if !stripes.is_empty() {
3040 let _ = tx.send(Instant::now());
3041 }
3042 })
3043 })
3044 };
3045 thread::sleep(Duration::from_secs(2));
3046 while rx.try_recv().is_ok() {}
3047 let painter = Painter::connect();
3048 let mut waits = Vec::new();
3049 for n in 0..60u32 {
3050 let color = 0x0010_2030u32.wrapping_mul(n + 1) & 0x00ff_ffff;
3051 match size {
3052 0 => painter.fill(color, 0, 0, width, height),
3053 _ => painter.fill(color, 64, 64, size, size),
3054 }
3055 let painted = Instant::now();
3056 if let Ok(sent) = rx.recv_timeout(Duration::from_millis(100)) {
3057 waits.push(sent.saturating_duration_since(painted).as_secs_f64() * 1000.0);
3058 }
3059 thread::sleep(Duration::from_millis(100));
3060 while rx.try_recv().is_ok() {}
3061 }
3062 controls.stop.store(true, Ordering::Relaxed);
3063 capture.join().expect("capture").expect("capture ran");
3064 waits.sort_by(f64::total_cmp);
3065 let at = |q: f64| waits[((waits.len() - 1) as f64 * q).round() as usize];
3066 println!(
3067 "{} {}x{} box {size}: change to frame p50 {:.2} ms, p99 {:.2} ms over {} changes",
3068 controls.report.info().capture,
3069 width,
3070 height,
3071 at(0.5),
3072 at(0.99),
3073 waits.len()
3074 );
3075 }
3076
3077 fn noise(width: usize, height: usize, mut seed: u32) -> Vec<u8> {
3079 (0..width * height)
3080 .flat_map(|_| {
3081 seed ^= seed << 13;
3082 seed ^= seed >> 17;
3083 seed ^= seed << 5;
3084 [seed as u8, (seed >> 8) as u8, (seed >> 16) as u8, 0]
3085 })
3086 .collect()
3087 }
3088
3089 #[cfg(feature = "gpl")]
3095 #[test]
3096 #[ignore]
3097 fn x11_ledger_refines_the_still_area_beside_motion() {
3098 refines_the_still_area_beside_motion(true);
3099 }
3100
3101 #[test]
3104 #[ignore]
3105 fn gpu_dri3_ledger_refines_the_still_area_beside_motion() {
3106 refines_the_still_area_beside_motion(false);
3107 }
3108
3109 fn refines_the_still_area_beside_motion(use_cpu: bool) {
3111 use crate::cleanup_bench::{psnr_outside, source_luma};
3112 use crate::encoders::codec::{Codec, VIDEO_HEADER_LEN};
3113 use crate::webcam::decode::{Codec as DecCodec, Decoder, VideoDecoder};
3114 use x11rb::protocol::xproto::ImageFormat;
3115 let (w, h) = (512u16, 256u16);
3116 let painter = Painter::connect();
3117 let gc = painter.conn.generate_id().expect("id");
3118 painter
3119 .conn
3120 .create_gc(gc, painter.root, &CreateGCAux::new())
3121 .expect("gc");
3122 let put = |pixels: &[u8], x: i16, y: i16, pw: u16, ph: u16| {
3123 painter
3124 .conn
3125 .put_image(
3126 ImageFormat::Z_PIXMAP,
3127 painter.root,
3128 gc,
3129 pw,
3130 ph,
3131 x,
3132 y,
3133 0,
3134 24,
3135 pixels,
3136 )
3137 .expect("put")
3138 .check()
3139 .expect("put done");
3140 };
3141 put(&noise(w as usize, h as usize, 7), 0, 0, w, h);
3142 let settings = CaptureSettings {
3143 codec: Codec::H264,
3144 use_cpu,
3145 encode_node_index: if use_cpu {
3146 -1
3147 } else {
3148 crate::auto_render_node("true")
3149 .and_then(|node| crate::render_node_index(&node))
3150 .unwrap_or(0)
3151 },
3152 video_streaming_mode: false,
3153 video_cbr_mode: false,
3154 video_crf: 35,
3155 video_paintover_crf: 18,
3156 use_paint_over_quality: true,
3157 paint_over_trigger_frames: 15,
3158 target_fps: 60.0,
3159 width: i32::from(w),
3160 height: i32::from(h),
3161 capture_cursor: false,
3162 ..Default::default()
3163 };
3164 let controls = Arc::new(Controls::new(&settings));
3165 let (tx, rx) = std::sync::mpsc::channel::<Vec<u8>>();
3166 let (tid_tx, _tid_rx) = std::sync::mpsc::channel();
3167 let capture = {
3168 let controls = controls.clone();
3169 thread::spawn(move || {
3170 run_capture(settings, controls, tid_tx, move |stripes| {
3171 for s in stripes {
3172 let _ = tx.send(s.data.to_vec());
3173 }
3174 })
3175 })
3176 };
3177 let area = (384usize, 128usize, 128usize, 128usize);
3178 let start = Instant::now();
3179 let mut n = 0u32;
3180 while start.elapsed() < Duration::from_secs(4) {
3181 put(&noise(128, 128, n + 1), 384, 128, 128, 128);
3182 n += 1;
3183 thread::sleep(Duration::from_millis(16));
3184 }
3185 controls.stop.store(true, Ordering::Relaxed);
3186 capture.join().expect("capture").expect("capture ran");
3187 let mut dec = VideoDecoder::new(DecCodec::H264).expect("decoder");
3188 let mut shown = vec![0u8; w as usize * h as usize];
3189 for pkt in rx.try_iter() {
3190 if dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode") {
3191 let pic = dec.frame().expect("picture");
3192 for r in 0..h as usize {
3193 shown[r * w as usize..(r + 1) * w as usize]
3194 .copy_from_slice(&pic.y[r * pic.y_stride..][..w as usize]);
3195 }
3196 }
3197 }
3198 let image = painter
3199 .conn
3200 .get_image(ImageFormat::Z_PIXMAP, painter.root, 0, 0, w, h, !0)
3201 .expect("get")
3202 .reply()
3203 .expect("image");
3204 let around = psnr_outside(
3205 &shown,
3206 &source_luma(&image.data, Codec::H264),
3207 w as usize,
3208 area,
3209 );
3210 let info = controls.report.info();
3211 println!("{}: around the box {around:.2} dB", info.capture);
3212 assert!(
3213 around > 40.0,
3214 "{}: the still area reached {around:.2} dB",
3215 info.capture
3216 );
3217 }
3218}
3219
3220#[cfg(test)]
3221mod vblank_tests {
3222 use super::*;
3225
3226 fn capture_at(fps: f64) -> Arc<Controls> {
3227 Arc::new(Controls::new(&CaptureSettings {
3228 target_fps: fps,
3229 ..Default::default()
3230 }))
3231 }
3232
3233 fn published() -> (Option<u32>, Option<u32>) {
3235 (
3236 property(b"_FAKE_SCREEN_FPS"),
3237 property(b"_FAKE_SCREEN_MILLIHZ"),
3238 )
3239 }
3240
3241 fn property(name: &[u8]) -> Option<u32> {
3242 let (conn, screen) = x11rb::connect(None).expect("connect");
3243 let root = conn.setup().roots[screen].root;
3244 let atom = conn
3245 .intern_atom(false, name)
3246 .expect("intern")
3247 .reply()
3248 .expect("atom")
3249 .atom;
3250 let reply = conn
3251 .get_property(false, root, atom, AtomEnum::CARDINAL, 0, 1)
3252 .expect("get_property")
3253 .reply()
3254 .expect("reply");
3255 reply.value32().and_then(|mut v| v.next())
3256 }
3257
3258 #[test]
3261 #[ignore]
3262 fn x11_vblank_rate_follows_the_fastest_capture() {
3263 let fast = capture_at(144.0);
3264 let slow = capture_at(90.0);
3265 let fast_claim = VblankClaim::new(&fast);
3266 let slow_claim = VblankClaim::new(&slow);
3267 assert_eq!(published(), (Some(144), Some(144_000)));
3268 drop(fast_claim);
3269 assert_eq!(published(), (Some(90), Some(90_000)));
3270 slow.fps_bits.store(119.6f64.to_bits(), Ordering::Relaxed);
3271 follow_frame_rate();
3272 assert_eq!(published(), (Some(120), Some(119_600)));
3273 slow.fps_bits
3274 .store((60000.0f64 / 1001.0).to_bits(), Ordering::Relaxed);
3275 follow_frame_rate();
3276 assert_eq!(published(), (Some(60), Some(59_940)));
3277 drop(slow_claim);
3278 assert_eq!(published(), (None, None));
3279 }
3280}