Skip to main content

kobo_core/rendering/
eink.rs

1//! E-ink framebuffer constants, MXCFB ioctl structs, and row-diff helper.
2//!
3//! The `Fb` struct (mmap + ioctl + `present()`) lives in
4//! [`crate::device::fb`]; it takes raw `&[u8]` RGB565 buffers, so it has no
5//! pixel-wrapper dependency.
6
7pub const FBIOGET_VSCREENINFO: libc::c_ulong = 0x4600;
8pub const FBIOGET_FSCREENINFO: libc::c_ulong = 0x4602;
9pub const MXCFB_SEND_UPDATE: libc::c_ulong = 0x4024462E;
10
11#[repr(C)]
12#[derive(Default)]
13pub struct FbVarScreeninfo {
14    pub xres: u32,
15    pub yres: u32,
16    pub xres_virtual: u32,
17    pub yres_virtual: u32,
18    pub xoffset: u32,
19    pub yoffset: u32,
20    pub bits_per_pixel: u32,
21    pub grayscale: u32,
22    pub rest: [u32; 32],
23}
24
25#[repr(C)]
26#[derive(Default)]
27pub struct FbFixScreeninfo {
28    id: [u8; 16],
29    pub smem_start: usize,
30    pub smem_len: u32,
31    typ: u32,
32    type_aux: u32,
33    visual: u32,
34    xpanstep: u16,
35    ypanstep: u16,
36    ywrapstep: u16,
37    pub line_length: u32,
38    rest: [u32; 16],
39}
40
41#[repr(C)]
42pub struct MxcfbRect {
43    pub top: u32,
44    pub left: u32,
45    pub width: u32,
46    pub height: u32,
47}
48
49#[repr(C)]
50pub struct MxcfbUpdateData {
51    pub update_region: MxcfbRect,
52    pub waveform_mode: u32,
53    pub update_mode: u32,
54    pub update_marker: u32,
55    pub temp: u32,
56    pub flags: u32,
57}
58
59pub const WAVE_INIT: u32 = 0;
60pub const WAVE_DU: u32 = 1;
61pub const WAVE_GC16: u32 = 2;
62pub const WAVE_GL16: u32 = 3;
63pub const WAVE_A2: u32 = 4;
64pub const WAVE_GLR16: u32 = 5;
65pub const WAVE_GLD16: u32 = 6;
66
67/// Pick the best waveform for a given render scenario on Kaleido 3 color e-ink.
68///
69/// - `Transition`: panel open/close, chapter overlay toggle. Needs good clearing
70///   to avoid ghosting, but no full flash. `GC16` partial clears better than
71///   `GL16` for large-area changes.
72/// - `Content`: regular text page updates. `GL16` has less ghosting on partial
73///   updates than `GC16`, preserving color quality for highlighted text and
74///   the reading cursor.
75/// - `Animation`: spinner, loading bar. `A2` is fastest (monochrome).
76pub fn waveform_for(scenario: RenderScenario) -> u32 {
77    match scenario {
78        RenderScenario::Transition => WAVE_GL16,
79        RenderScenario::Content => WAVE_GL16,
80        RenderScenario::Animation => WAVE_A2,
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum RenderScenario {
86    Transition,
87    Content,
88    Animation,
89}
90
91/// Compare two RGB565 byte buffers (same dimensions) and return the dirty row
92/// range aligned to 8-pixel boundaries. Returns `None` when buffers are
93/// identical.
94///
95/// `prev` and `cur` are raw byte views of `w * h` RGB565 pixels (2 bytes each,
96/// stride = `w * 2`). The caller is responsible for providing matching-length
97/// slices - typically via a `rgb565_as_bytes` helper on the pixel type.
98pub fn diff_rows(prev: &[u8], cur: &[u8], w: usize, h: usize) -> Option<(usize, usize)> {
99    let mut min_y = h;
100    let mut max_y = 0;
101    let mut dirty = false;
102    for y in 0..h {
103        let base = y * w * 2;
104        if prev[base..base + w * 2] != cur[base..base + w * 2] {
105            dirty = true;
106            if y < min_y {
107                min_y = y;
108            }
109            if y > max_y {
110                max_y = y;
111            }
112        }
113    }
114    if !dirty {
115        return None;
116    }
117    const A: usize = 8;
118    let top = (min_y / A) * A;
119    let bottom = ((max_y + A) / A) * A;
120    let rh = bottom.min(h) - top;
121    Some((top, rh))
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    fn make_buf(w: usize, h: usize, fill: u8) -> Vec<u8> {
129        vec![fill; w * h * 2]
130    }
131
132    #[test]
133    fn diff_rows_detects_change() {
134        let w = 10;
135        let h = 16;
136        let prev = make_buf(w, h, 0);
137        let mut cur = prev.clone();
138        let px = (5 * w + 3) * 2;
139        cur[px] = 1;
140        cur[px + 1] = 1;
141        let (top, rh) = diff_rows(&prev, &cur, w, h).unwrap();
142        assert_eq!(top, 0);
143        assert!(rh > 0);
144    }
145
146    #[test]
147    fn diff_rows_returns_none_when_identical() {
148        let w = 10;
149        let h = 10;
150        let buf = make_buf(w, h, 0);
151        assert!(diff_rows(&buf, &buf, w, h).is_none());
152    }
153
154    #[test]
155    fn diff_rows_aligns_to_8px_boundary() {
156        let w = 10;
157        let h = 32;
158        let prev = make_buf(w, h, 0);
159        let mut cur = prev.clone();
160        let px = (13 * w) * 2;
161        cur[px] = 1;
162        let (top, rh) = diff_rows(&prev, &cur, w, h).unwrap();
163        assert_eq!(top, 8);
164        assert_eq!(rh, 8);
165    }
166}