use alloc::vec::Vec;
use rlvgl_core::invalidation::{InvalidationList, InvalidationSource, PresentPlan};
use rlvgl_core::widget::{Color, Rect};
use crate::blit::BlitPlanner;
use crate::display::DisplayDriver;
pub fn present_plan(
driver: &mut dyn DisplayDriver,
plan: PresentPlan<'_>,
frame: &[Color],
) -> usize {
let screen = driver.screen();
let (lw, lh) = screen.logical_size();
match plan {
PresentPlan::None => 0,
PresentPlan::FullFrame => {
let full = Rect {
x: 0,
y: 0,
width: lw as i32,
height: lh as i32,
};
driver.flush(full, frame);
driver.vsync();
1
}
PresentPlan::Rects(rects) => {
let stride = lw as usize;
let mut count = 0usize;
let mut buf: Vec<Color> = Vec::new();
for &rect in rects {
let rx_end = rect.x.saturating_add(rect.width);
let ry_end = rect.y.saturating_add(rect.height);
let valid = rect.x >= 0
&& rect.y >= 0
&& rect.width > 0
&& rect.height > 0
&& rx_end <= lw as i32
&& ry_end <= lh as i32;
debug_assert!(
valid,
"present_plan: rect {:?} is outside frame {}x{}",
rect, lw, lh
);
if !valid {
continue;
}
let w = rect.width as usize;
let h = rect.height as usize;
buf.resize(w * h, Color(0, 0, 0, 0));
for row in 0..h {
let src_start = (rect.y as usize + row) * stride + rect.x as usize;
let dst_start = row * w;
buf[dst_start..dst_start + w].copy_from_slice(&frame[src_start..src_start + w]);
}
driver.flush(rect, &buf);
count += 1;
}
if count > 0 {
driver.vsync();
}
count
}
}
}
pub fn ingest_blit_planner<const N: usize, const M: usize>(
planner: &BlitPlanner<N>,
list: &mut InvalidationList<M>,
) {
if planner.overflowed() {
list.mark_full_frame();
return;
}
for br in planner.rects() {
let rect = Rect {
x: br.x,
y: br.y,
width: br.w as i32,
height: br.h as i32,
};
list.push(rect);
}
}
pub struct SliceSource<'a> {
rects: &'a [Rect],
}
impl<'a> SliceSource<'a> {
pub fn new(rects: &'a [Rect]) -> Self {
Self { rects }
}
}
impl InvalidationSource for SliceSource<'_> {
fn collect_dirty(&mut self, sink: &mut dyn FnMut(Rect)) {
for &r in self.rects {
sink(r);
}
}
}
#[cfg(test)]
mod tests {
use rlvgl_core::invalidation::{BufferedInvalidation, InvalidationList, PresentPlan};
use rlvgl_core::widget::{Color, Rect};
use super::*;
use crate::blit::BlitPlanner;
use crate::display::{BufferDisplay, DisplayDriver};
fn r(x: i32, y: i32, w: i32, h: i32) -> Rect {
Rect {
x,
y,
width: w,
height: h,
}
}
fn red() -> Color {
Color(255, 0, 0, 255)
}
fn green() -> Color {
Color(0, 255, 0, 255)
}
fn blue() -> Color {
Color(0, 0, 255, 255)
}
fn black() -> Color {
Color(0, 0, 0, 255)
}
fn solid_frame(w: usize, h: usize, fill: Color) -> alloc::vec::Vec<Color> {
alloc::vec![fill; w * h]
}
struct VsyncCounter<D: DisplayDriver> {
inner: D,
pub vsync_count: usize,
}
impl<D: DisplayDriver> VsyncCounter<D> {
fn new(inner: D) -> Self {
Self {
inner,
vsync_count: 0,
}
}
}
impl<D: DisplayDriver> DisplayDriver for VsyncCounter<D> {
fn screen(&self) -> crate::screen::Screen {
self.inner.screen()
}
fn flush(&mut self, area: Rect, colors: &[Color]) {
self.inner.flush(area, colors);
}
fn vsync(&mut self) {
self.vsync_count += 1;
}
}
#[test]
fn none_plan_issues_zero_flushes() {
let mut disp = BufferDisplay::new(10, 10);
let frame = solid_frame(10, 10, red());
let count = present_plan(&mut disp, PresentPlan::None, &frame);
assert_eq!(count, 0);
assert!(disp.buffer.iter().all(|&c| c == black()));
}
#[test]
fn none_plan_does_not_call_vsync() {
let mut disp = VsyncCounter::new(BufferDisplay::new(10, 10));
let frame = solid_frame(10, 10, red());
present_plan(&mut disp, PresentPlan::None, &frame);
assert_eq!(disp.vsync_count, 0);
}
#[test]
fn full_frame_plan_copies_entire_frame() {
let (w, h) = (8, 6);
let mut disp = BufferDisplay::new(w, h);
let frame = solid_frame(w, h, green());
let count = present_plan(&mut disp, PresentPlan::FullFrame, &frame);
assert_eq!(count, 1);
assert!(disp.buffer.iter().all(|&c| c == green()));
}
#[test]
fn full_frame_plan_calls_vsync_once() {
let mut disp = VsyncCounter::new(BufferDisplay::new(4, 4));
let frame = solid_frame(4, 4, blue());
present_plan(&mut disp, PresentPlan::FullFrame, &frame);
assert_eq!(disp.vsync_count, 1);
}
#[test]
fn multi_rect_pixels_land_at_correct_coordinates() {
let (w, h) = (8usize, 8usize);
let mut frame = solid_frame(w, h, green());
for py in 0..2usize {
for px in 0..2usize {
frame[py * w + px] = red();
}
}
for py in 6..8usize {
for px in 6..8usize {
frame[py * w + px] = blue();
}
}
let mut disp = BufferDisplay::new(w, h);
let rects = [r(0, 0, 2, 2), r(6, 6, 2, 2)];
let count = present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
assert_eq!(count, 2);
assert_eq!(disp.buffer[0], red());
assert_eq!(disp.buffer[1], red());
assert_eq!(disp.buffer[w], red());
assert_eq!(disp.buffer[w + 1], red());
assert_eq!(disp.buffer[6 * w + 6], blue());
assert_eq!(disp.buffer[6 * w + 7], blue());
assert_eq!(disp.buffer[7 * w + 6], blue());
assert_eq!(disp.buffer[7 * w + 7], blue());
assert_eq!(disp.buffer[3 * w + 3], black());
}
#[test]
fn untouched_pixels_keep_initial_color() {
let (w, h) = (6usize, 6usize);
let frame = solid_frame(w, h, red());
let mut disp = BufferDisplay::new(w, h);
let rects = [r(3, 3, 1, 1)];
present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
assert_eq!(disp.buffer[3 * w + 3], red());
assert_eq!(disp.buffer[0], black());
}
#[test]
fn flush_count_matches_rect_count() {
let (w, h) = (20usize, 10usize);
let frame = solid_frame(w, h, green());
let mut disp = BufferDisplay::new(w, h);
let rects = [r(0, 0, 4, 4), r(5, 0, 4, 4), r(10, 0, 4, 4)];
let count = present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
assert_eq!(count, 3);
}
#[test]
fn multi_rect_present_calls_vsync_exactly_once() {
let (w, h) = (10usize, 10usize);
let frame = solid_frame(w, h, blue());
let mut disp = VsyncCounter::new(BufferDisplay::new(w, h));
let rects = [r(0, 0, 3, 3), r(5, 5, 3, 3)];
present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
assert_eq!(disp.vsync_count, 1);
}
#[test]
fn empty_rects_slice_yields_zero_flushes_and_no_vsync() {
let (w, h) = (8usize, 8usize);
let frame = solid_frame(w, h, green());
let mut disp = VsyncCounter::new(BufferDisplay::new(w, h));
let count = present_plan(&mut disp, PresentPlan::Rects(&[]), &frame);
assert_eq!(count, 0);
assert_eq!(disp.vsync_count, 0);
}
fn blit_rect(x: i32, y: i32, w: u32, h: u32) -> crate::blit::Rect {
crate::blit::Rect { x, y, w, h }
}
#[test]
fn blit_planner_rects_transfer_into_invalidation_list() {
let mut planner: BlitPlanner<8> = BlitPlanner::new();
planner.add(blit_rect(0, 0, 10, 10));
planner.add(blit_rect(20, 0, 5, 5));
let mut list = InvalidationList::<8>::with_size(100, 100);
ingest_blit_planner(&planner, &mut list);
assert!(!list.is_full_frame());
assert_eq!(list.len(), 2);
assert_eq!(
list.plan(),
PresentPlan::Rects(&[r(0, 0, 10, 10), r(20, 0, 5, 5)])
);
}
#[test]
fn overflowed_blit_planner_promotes_to_full_frame() {
let mut planner: BlitPlanner<1> = BlitPlanner::new();
planner.add(blit_rect(0, 0, 10, 10));
planner.add(blit_rect(50, 50, 10, 10));
assert!(planner.overflowed());
let mut list = InvalidationList::<8>::with_size(100, 100);
ingest_blit_planner(&planner, &mut list);
assert_eq!(list.plan(), PresentPlan::FullFrame);
}
#[test]
fn non_overflowed_planner_does_not_promote_to_full_frame() {
let mut planner: BlitPlanner<4> = BlitPlanner::new();
planner.add(blit_rect(1, 1, 3, 3));
let mut list = InvalidationList::<8>::with_size(100, 100);
ingest_blit_planner(&planner, &mut list);
assert!(!list.is_full_frame());
}
#[test]
fn end_to_end_invalidation_list_to_buffer_display() {
let (w, h) = (20usize, 20usize);
let mut frame = solid_frame(w, h, green());
for py in 0..10usize {
for px in 0..10usize {
frame[py * w + px] = red();
}
}
let mut list = InvalidationList::<8>::with_size(w as i32, h as i32);
list.push(r(0, 0, 10, 10));
let plan = list.plan();
let mut disp = BufferDisplay::new(w, h);
let count = present_plan(&mut disp, plan, &frame);
assert_eq!(count, 1);
for py in 0..10usize {
for px in 0..10usize {
assert_eq!(disp.buffer[py * w + px], red(), "({px},{py}) should be red");
}
}
assert_eq!(disp.buffer[10 * w + 10], black());
}
#[test]
fn end_to_end_with_buffered_invalidation() {
let (w, h) = (16usize, 16usize);
let frame = solid_frame(w, h, blue());
let mut buf = BufferedInvalidation::<8, 2>::with_size(w as i32, h as i32);
buf.push(r(0, 0, 4, 4));
let plan = buf.plan();
let mut disp = BufferDisplay::new(w, h);
let count = present_plan(&mut disp, plan, &frame);
assert_eq!(count, 1);
buf.finish_present();
let plan2 = buf.plan();
assert!(matches!(plan2, PresentPlan::Rects(_)));
present_plan(&mut disp, plan2, &frame);
buf.finish_present();
assert_eq!(buf.plan(), PresentPlan::None);
}
#[test]
fn full_frame_plan_on_overflowed_list_flushes_whole_screen() {
let (w, h) = (8usize, 8usize);
let frame = solid_frame(w, h, green());
let mut disp = BufferDisplay::new(w, h);
let mut list = InvalidationList::<1>::with_size(w as i32, h as i32);
list.push(r(0, 0, 2, 2));
list.push(r(4, 4, 2, 2));
assert_eq!(list.plan(), PresentPlan::FullFrame);
let count = present_plan(&mut disp, list.plan(), &frame);
assert_eq!(count, 1);
assert!(disp.buffer.iter().all(|&c| c == green()));
}
}