Skip to main content

rlvgl_platform/
compositor.rs

1//! Save-under compositor for overlay framebuffer management.
2//!
3//! When an overlay becomes visible, the compositor saves the framebuffer
4//! pixels under it. When the overlay hides, the saved pixels are restored.
5//! This avoids needing a global pristine copy and handles overlapping
6//! overlays correctly (each saves what was under it at open time).
7
8use alloc::vec::Vec;
9use rlvgl_core::widget::Rect;
10
11/// Portrait framebuffer rectangle (after draw→fb coordinate transform).
12#[derive(Debug, Clone, Copy)]
13struct FbRect {
14    x: u32,
15    y: u32,
16    w: u32,
17    h: u32,
18}
19
20/// A saved framebuffer region with its pixel data.
21struct SaveUnder {
22    region: FbRect,
23    /// Saved ARGB8888 pixel data (row-major, `w * h * 4` bytes).
24    pixels: Vec<u8>,
25}
26
27/// Compositor for overlay framebuffer management.
28///
29/// Supports two restore modes:
30/// - **Pristine restore**: copies from a fixed reference framebuffer (fast, no allocation)
31/// - **Save-under**: saves pixels on show, restores on hide (handles dynamic content)
32///
33/// All public methods accept landscape draw coordinates; the compositor
34/// transforms to portrait framebuffer coordinates internally.
35pub struct Compositor {
36    /// Active save-under buffers, keyed by overlay ID.
37    saves: Vec<(u32, SaveUnder)>,
38    /// Pending restores for this frame (save-under).
39    pending_restore: Vec<SaveUnder>,
40    /// Pending pristine restores with frame countdown.
41    pending_pristine: Vec<(FbRect, u8)>,
42    /// Pristine framebuffer address (if set).
43    pristine_addr: usize,
44    /// Portrait framebuffer width (short axis, e.g. 480).
45    fb_width: u32,
46    /// Portrait framebuffer height (long axis, e.g. 800).
47    fb_height: u32,
48    /// Bytes per row.
49    stride: usize,
50    /// Next overlay ID.
51    next_id: u32,
52    /// Number of pristine regions copied during the most recent restore pass.
53    last_pristine_regions: u16,
54    /// Number of save-under regions copied during the most recent restore pass.
55    last_save_regions: u16,
56    /// Total bytes copied during the most recent restore pass.
57    last_restore_bytes: u32,
58    /// Monotonic restore sequence number.
59    restore_seq: u32,
60}
61
62impl Compositor {
63    /// Create a new compositor for the given portrait framebuffer dimensions.
64    ///
65    /// `pristine_addr` is the address of a reference background copy in SDRAM
66    /// (e.g. the desktop image). Pass 0 if no pristine copy is available.
67    pub fn new(fb_width: u32, fb_height: u32, pristine_addr: u32) -> Self {
68        Self {
69            saves: Vec::new(),
70            pending_restore: Vec::new(),
71            pending_pristine: Vec::new(),
72            pristine_addr: pristine_addr as usize,
73            fb_width,
74            fb_height,
75            stride: (fb_width * 4) as usize,
76            next_id: 1,
77            last_pristine_regions: 0,
78            last_save_regions: 0,
79            last_restore_bytes: 0,
80            restore_seq: 0,
81        }
82    }
83
84    /// Allocate an overlay ID for save-under management.
85    pub fn register_overlay(&mut self) -> u32 {
86        let id = self.next_id;
87        self.next_id += 1;
88        id
89    }
90
91    /// Transform landscape draw rect to portrait fb rect.
92    fn draw_to_fb(&self, draw_rect: Rect) -> FbRect {
93        let fb_x = (self.fb_width as i32 - draw_rect.y - draw_rect.height).max(0) as u32;
94        let fb_y = draw_rect.x.max(0) as u32;
95        let fb_w = (draw_rect.height as u32).min(self.fb_width.saturating_sub(fb_x));
96        let fb_h = (draw_rect.width as u32).min(self.fb_height.saturating_sub(fb_y));
97        FbRect {
98            x: fb_x,
99            y: fb_y,
100            w: fb_w,
101            h: fb_h,
102        }
103    }
104
105    /// Save the framebuffer pixels under an overlay region.
106    ///
107    /// Call when an overlay becomes visible. `draw_rect` is in landscape
108    /// draw coordinates. Reads from the **front** buffer (what's currently
109    /// displayed) since the back buffer may not have been rendered yet.
110    ///
111    /// # Safety
112    /// `front_buffer` must point to a valid framebuffer.
113    pub unsafe fn save(&mut self, overlay_id: u32, draw_rect: Rect, front_buffer: *const u8) {
114        let fb = self.draw_to_fb(draw_rect);
115        if fb.w == 0 || fb.h == 0 {
116            return;
117        }
118        let stride = self.stride;
119        let mut pixels = Vec::with_capacity((fb.w * fb.h * 4) as usize);
120        for row in 0..fb.h {
121            let y = fb.y + row;
122            let off = y as usize * stride + fb.x as usize * 4;
123            let len = fb.w as usize * 4;
124            unsafe {
125                let src = core::slice::from_raw_parts(front_buffer.add(off), len);
126                pixels.extend_from_slice(src);
127            }
128        }
129        // Remove any existing save for this overlay
130        self.saves.retain(|(id, _)| *id != overlay_id);
131        self.saves
132            .push((overlay_id, SaveUnder { region: fb, pixels }));
133    }
134
135    /// Queue restoration from the pristine background copy.
136    ///
137    /// Use this for overlays over static backgrounds (desktop image).
138    /// No heap allocation — reads directly from the pristine fb address.
139    /// Queue restoration from pristine for multiple frames (double-buffer).
140    pub fn mark_pristine_restore(&mut self, draw_rect: Rect) {
141        let fb = self.draw_to_fb(draw_rect);
142        if fb.w > 0 && fb.h > 0 {
143            // 3 frames: both buffers + 1 safety margin
144            self.pending_pristine.push((fb, 3));
145        }
146    }
147
148    /// Queue restoration of the saved pixels for an overlay.
149    ///
150    /// Call when an overlay hides. The actual restore happens on the next
151    /// `restore()` call. The save buffer is consumed.
152    pub fn mark_restore(&mut self, overlay_id: u32) {
153        if let Some(pos) = self.saves.iter().position(|(id, _)| *id == overlay_id) {
154            let (_, save) = self.saves.remove(pos);
155            self.pending_restore.push(save);
156        }
157    }
158
159    /// Restore all pending save-under regions to the back buffer.
160    ///
161    /// Call before drawing the widget tree each frame.
162    ///
163    /// # Safety
164    /// `back_buffer` must point to a valid framebuffer.
165    pub unsafe fn restore(&mut self, back_buffer: *mut u8) {
166        let stride = self.stride;
167        let mut pristine_regions = 0u16;
168        let mut save_regions = 0u16;
169        let mut restore_bytes = 0u32;
170
171        // Restore from pristine background copy (multi-frame for double-buffer)
172        if self.pristine_addr != 0 {
173            let prist = self.pristine_addr as *const u8;
174            for (region, _) in &self.pending_pristine {
175                pristine_regions = pristine_regions.saturating_add(1);
176                for row in 0..region.h {
177                    let y = region.y + row;
178                    let off = y as usize * stride + region.x as usize * 4;
179                    let len = region.w as usize * 4;
180                    unsafe {
181                        core::ptr::copy_nonoverlapping(prist.add(off), back_buffer.add(off), len);
182                    }
183                    restore_bytes = restore_bytes.saturating_add(len as u32);
184                }
185            }
186        }
187        // Decrement countdowns, remove expired
188        for entry in &mut self.pending_pristine {
189            entry.1 = entry.1.saturating_sub(1);
190        }
191        self.pending_pristine.retain(|e| e.1 > 0);
192
193        // Restore from save-under buffers
194        for save in &self.pending_restore {
195            save_regions = save_regions.saturating_add(1);
196            let fb = &save.region;
197            let row_bytes = fb.w as usize * 4;
198            for row in 0..fb.h {
199                let y = fb.y + row;
200                let fb_off = y as usize * stride + fb.x as usize * 4;
201                let save_off = row as usize * row_bytes;
202                unsafe {
203                    core::ptr::copy_nonoverlapping(
204                        save.pixels.as_ptr().add(save_off),
205                        back_buffer.add(fb_off),
206                        row_bytes,
207                    );
208                }
209                restore_bytes = restore_bytes.saturating_add(row_bytes as u32);
210            }
211        }
212        self.pending_restore.clear();
213        self.last_pristine_regions = pristine_regions;
214        self.last_save_regions = save_regions;
215        self.last_restore_bytes = restore_bytes;
216        self.restore_seq = self.restore_seq.wrapping_add(1);
217    }
218
219    /// Whether there are pending restores of any kind.
220    pub fn has_pending(&self) -> bool {
221        !self.pending_restore.is_empty() || !self.pending_pristine.is_empty()
222    }
223
224    /// Return a packed summary of compositor queue state.
225    pub fn diag_counts(&self) -> u32 {
226        ((self.saves.len().min(0xFF) as u32) << 24)
227            | ((self.pending_restore.len().min(0xFF) as u32) << 16)
228            | ((self.pending_pristine.len().min(0xFF) as u32) << 8)
229            | ((self.last_pristine_regions.min(0x0F) as u32) << 4)
230            | (self.last_save_regions.min(0x0F) as u32)
231    }
232
233    /// Return the most recent restore sequence and byte count.
234    pub fn diag_bytes(&self) -> u32 {
235        ((self.restore_seq & 0xFFFF) << 16) | (self.last_restore_bytes.min(0xFFFF))
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn draw_to_fb_transforms_correctly() {
245        let comp = Compositor::new(480, 800, 0);
246        let fb = comp.draw_to_fb(Rect {
247            x: 100,
248            y: 50,
249            width: 200,
250            height: 100,
251        });
252        // fb_x = 480 - 50 - 100 = 330, fb_y = 100, fb_w = 100, fb_h = 200
253        assert_eq!((fb.x, fb.y, fb.w, fb.h), (330, 100, 100, 200));
254    }
255
256    #[test]
257    fn register_overlay_increments() {
258        let mut comp = Compositor::new(480, 800, 0);
259        assert_eq!(comp.register_overlay(), 1);
260        assert_eq!(comp.register_overlay(), 2);
261    }
262}