1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//! Owned, tightly packed capture pixels shared by platform backends.
/// Bytes per pixel in the `DXGI_FORMAT_B8G8R8A8_UNORM` frames the
/// duplication hands back. Named so the pitch arithmetic below reads as
/// intent rather than as a bare `4`.
pub const BYTES_PER_PIXEL: usize = 4;
/// A rectangle of the desktop that changed since the previous frame.
///
/// Mirrors Win32 `RECT` (right/bottom exclusive) but kept as our own type
/// so callers — and, later, the wire format — never need the `windows`
/// crate in their signatures.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DirtyRect {
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
}
impl DirtyRect {
/// Pixel area, saturating at zero. DXGI is expected to hand back
/// well-formed rectangles; clamping rather than trusting the
/// subtraction keeps a degenerate one from wrapping the `u64` area
/// accumulator into a nonsense number.
#[cfg(target_os = "windows")]
pub fn area_px(&self) -> u64 {
let w = (self.right - self.left).max(0) as u64;
let h = (self.bottom - self.top).max(0) as u64;
w * h
}
}
/// One captured desktop frame, already copied out of GPU memory.
#[derive(Debug, Clone)]
pub struct Frame {
pub width: u32,
pub height: u32,
/// Tightly packed BGRA, `width * height * BYTES_PER_PIXEL` bytes. The
/// GPU's row pitch (which is padded, and routinely wider than
/// `width * 4`) has already been removed, so consumers can treat this
/// as a plain image buffer.
pub bgra: Vec<u8>,
/// Regions that changed since the previous frame. **Empty means "we
/// could not tell"**, not "nothing changed" — the encoder sends the full
/// frame in that case.
pub dirty_rects: Vec<DirtyRect>,
/// How many desktop updates the compositor coalesced into this one
/// frame. A value above 1 means we are sampling slower than the
/// desktop is changing.
#[cfg(target_os = "windows")]
pub accumulated_frames: u32,
}
#[cfg(target_os = "windows")]
impl Frame {
/// Total changed area. Note this **double-counts overlapping
/// rectangles**: it is a cheap upper bound for bandwidth estimation,
/// not an exact changed-pixel count. Computing the true union needs a
/// sweep-line over the rects, which is not worth it for a metric whose
/// only job is to answer "is a dirty-rect encoder worth building".
pub fn dirty_area_px(&self) -> u64 {
self.dirty_rects.iter().map(DirtyRect::area_px).sum()
}
/// Full-frame pixel count — the denominator for "what fraction of the
/// screen changed".
pub fn total_px(&self) -> u64 {
u64::from(self.width) * u64::from(self.height)
}
}