1use alloc::vec::Vec;
15use rlvgl_core::invalidation::{InvalidationList, InvalidationSource, PresentPlan};
16use rlvgl_core::widget::{Color, Rect};
17
18use crate::blit::BlitPlanner;
19use crate::display::DisplayDriver;
20
21pub fn present_plan(
46 driver: &mut dyn DisplayDriver,
47 plan: PresentPlan<'_>,
48 frame: &[Color],
49) -> usize {
50 let screen = driver.screen();
51 let (lw, lh) = screen.logical_size();
52
53 match plan {
54 PresentPlan::None => 0,
55
56 PresentPlan::FullFrame => {
57 let full = Rect {
58 x: 0,
59 y: 0,
60 width: lw as i32,
61 height: lh as i32,
62 };
63 driver.flush(full, frame);
64 driver.vsync();
65 1
66 }
67
68 PresentPlan::Rects(rects) => {
69 let stride = lw as usize;
70 let mut count = 0usize;
71 let mut buf: Vec<Color> = Vec::new();
72
73 for &rect in rects {
74 let rx_end = rect.x.saturating_add(rect.width);
78 let ry_end = rect.y.saturating_add(rect.height);
79 let valid = rect.x >= 0
80 && rect.y >= 0
81 && rect.width > 0
82 && rect.height > 0
83 && rx_end <= lw as i32
84 && ry_end <= lh as i32;
85
86 debug_assert!(
87 valid,
88 "present_plan: rect {:?} is outside frame {}x{}",
89 rect, lw, lh
90 );
91
92 if !valid {
93 continue;
94 }
95
96 let w = rect.width as usize;
97 let h = rect.height as usize;
98 buf.resize(w * h, Color(0, 0, 0, 0));
99
100 for row in 0..h {
101 let src_start = (rect.y as usize + row) * stride + rect.x as usize;
102 let dst_start = row * w;
103 buf[dst_start..dst_start + w].copy_from_slice(&frame[src_start..src_start + w]);
104 }
105
106 driver.flush(rect, &buf);
107 count += 1;
108 }
109
110 if count > 0 {
111 driver.vsync();
112 }
113 count
114 }
115 }
116}
117
118pub fn ingest_blit_planner<const N: usize, const M: usize>(
134 planner: &BlitPlanner<N>,
135 list: &mut InvalidationList<M>,
136) {
137 if planner.overflowed() {
140 list.mark_full_frame();
141 return;
142 }
143
144 for br in planner.rects() {
145 let rect = Rect {
148 x: br.x,
149 y: br.y,
150 width: br.w as i32,
151 height: br.h as i32,
152 };
153 list.push(rect);
154 }
155}
156
157pub struct SliceSource<'a> {
164 rects: &'a [Rect],
165}
166
167impl<'a> SliceSource<'a> {
168 pub fn new(rects: &'a [Rect]) -> Self {
170 Self { rects }
171 }
172}
173
174impl InvalidationSource for SliceSource<'_> {
175 fn collect_dirty(&mut self, sink: &mut dyn FnMut(Rect)) {
176 for &r in self.rects {
177 sink(r);
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use rlvgl_core::invalidation::{BufferedInvalidation, InvalidationList, PresentPlan};
185 use rlvgl_core::widget::{Color, Rect};
186
187 use super::*;
188 use crate::blit::BlitPlanner;
189 use crate::display::{BufferDisplay, DisplayDriver};
190
191 fn r(x: i32, y: i32, w: i32, h: i32) -> Rect {
196 Rect {
197 x,
198 y,
199 width: w,
200 height: h,
201 }
202 }
203
204 fn red() -> Color {
205 Color(255, 0, 0, 255)
206 }
207 fn green() -> Color {
208 Color(0, 255, 0, 255)
209 }
210 fn blue() -> Color {
211 Color(0, 0, 255, 255)
212 }
213 fn black() -> Color {
214 Color(0, 0, 0, 255)
215 }
216
217 fn solid_frame(w: usize, h: usize, fill: Color) -> alloc::vec::Vec<Color> {
220 alloc::vec![fill; w * h]
221 }
222
223 struct VsyncCounter<D: DisplayDriver> {
228 inner: D,
229 pub vsync_count: usize,
231 }
232
233 impl<D: DisplayDriver> VsyncCounter<D> {
234 fn new(inner: D) -> Self {
235 Self {
236 inner,
237 vsync_count: 0,
238 }
239 }
240 }
241
242 impl<D: DisplayDriver> DisplayDriver for VsyncCounter<D> {
243 fn screen(&self) -> crate::screen::Screen {
244 self.inner.screen()
245 }
246 fn flush(&mut self, area: Rect, colors: &[Color]) {
247 self.inner.flush(area, colors);
248 }
249 fn vsync(&mut self) {
250 self.vsync_count += 1;
251 }
252 }
253
254 #[test]
259 fn none_plan_issues_zero_flushes() {
260 let mut disp = BufferDisplay::new(10, 10);
261 let frame = solid_frame(10, 10, red());
262 let count = present_plan(&mut disp, PresentPlan::None, &frame);
263 assert_eq!(count, 0);
264 assert!(disp.buffer.iter().all(|&c| c == black()));
266 }
267
268 #[test]
269 fn none_plan_does_not_call_vsync() {
270 let mut disp = VsyncCounter::new(BufferDisplay::new(10, 10));
271 let frame = solid_frame(10, 10, red());
272 present_plan(&mut disp, PresentPlan::None, &frame);
273 assert_eq!(disp.vsync_count, 0);
274 }
275
276 #[test]
277 fn full_frame_plan_copies_entire_frame() {
278 let (w, h) = (8, 6);
279 let mut disp = BufferDisplay::new(w, h);
280 let frame = solid_frame(w, h, green());
281 let count = present_plan(&mut disp, PresentPlan::FullFrame, &frame);
282 assert_eq!(count, 1);
283 assert!(disp.buffer.iter().all(|&c| c == green()));
284 }
285
286 #[test]
287 fn full_frame_plan_calls_vsync_once() {
288 let mut disp = VsyncCounter::new(BufferDisplay::new(4, 4));
289 let frame = solid_frame(4, 4, blue());
290 present_plan(&mut disp, PresentPlan::FullFrame, &frame);
291 assert_eq!(disp.vsync_count, 1);
292 }
293
294 #[test]
295 fn multi_rect_pixels_land_at_correct_coordinates() {
296 let (w, h) = (8usize, 8usize);
299 let mut frame = solid_frame(w, h, green());
300 for py in 0..2usize {
302 for px in 0..2usize {
303 frame[py * w + px] = red();
304 }
305 }
306 for py in 6..8usize {
308 for px in 6..8usize {
309 frame[py * w + px] = blue();
310 }
311 }
312
313 let mut disp = BufferDisplay::new(w, h);
314 let rects = [r(0, 0, 2, 2), r(6, 6, 2, 2)];
315 let count = present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
316 assert_eq!(count, 2);
317
318 assert_eq!(disp.buffer[0], red());
320 assert_eq!(disp.buffer[1], red());
321 assert_eq!(disp.buffer[w], red());
322 assert_eq!(disp.buffer[w + 1], red());
323
324 assert_eq!(disp.buffer[6 * w + 6], blue());
325 assert_eq!(disp.buffer[6 * w + 7], blue());
326 assert_eq!(disp.buffer[7 * w + 6], blue());
327 assert_eq!(disp.buffer[7 * w + 7], blue());
328
329 assert_eq!(disp.buffer[3 * w + 3], black());
331 }
332
333 #[test]
334 fn untouched_pixels_keep_initial_color() {
335 let (w, h) = (6usize, 6usize);
336 let frame = solid_frame(w, h, red());
337 let mut disp = BufferDisplay::new(w, h);
338 let rects = [r(3, 3, 1, 1)];
340 present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
341 assert_eq!(disp.buffer[3 * w + 3], red());
343 assert_eq!(disp.buffer[0], black());
345 }
346
347 #[test]
348 fn flush_count_matches_rect_count() {
349 let (w, h) = (20usize, 10usize);
350 let frame = solid_frame(w, h, green());
351 let mut disp = BufferDisplay::new(w, h);
352 let rects = [r(0, 0, 4, 4), r(5, 0, 4, 4), r(10, 0, 4, 4)];
353 let count = present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
354 assert_eq!(count, 3);
355 }
356
357 #[test]
358 fn multi_rect_present_calls_vsync_exactly_once() {
359 let (w, h) = (10usize, 10usize);
360 let frame = solid_frame(w, h, blue());
361 let mut disp = VsyncCounter::new(BufferDisplay::new(w, h));
362 let rects = [r(0, 0, 3, 3), r(5, 5, 3, 3)];
363 present_plan(&mut disp, PresentPlan::Rects(&rects), &frame);
364 assert_eq!(disp.vsync_count, 1);
365 }
366
367 #[test]
368 fn empty_rects_slice_yields_zero_flushes_and_no_vsync() {
369 let (w, h) = (8usize, 8usize);
370 let frame = solid_frame(w, h, green());
371 let mut disp = VsyncCounter::new(BufferDisplay::new(w, h));
372 let count = present_plan(&mut disp, PresentPlan::Rects(&[]), &frame);
373 assert_eq!(count, 0);
374 assert_eq!(disp.vsync_count, 0);
375 }
376
377 fn blit_rect(x: i32, y: i32, w: u32, h: u32) -> crate::blit::Rect {
382 crate::blit::Rect { x, y, w, h }
383 }
384
385 #[test]
386 fn blit_planner_rects_transfer_into_invalidation_list() {
387 let mut planner: BlitPlanner<8> = BlitPlanner::new();
388 planner.add(blit_rect(0, 0, 10, 10));
389 planner.add(blit_rect(20, 0, 5, 5));
390
391 let mut list = InvalidationList::<8>::with_size(100, 100);
392 ingest_blit_planner(&planner, &mut list);
393
394 assert!(!list.is_full_frame());
395 assert_eq!(list.len(), 2);
396 assert_eq!(
397 list.plan(),
398 PresentPlan::Rects(&[r(0, 0, 10, 10), r(20, 0, 5, 5)])
399 );
400 }
401
402 #[test]
403 fn overflowed_blit_planner_promotes_to_full_frame() {
404 let mut planner: BlitPlanner<1> = BlitPlanner::new();
406 planner.add(blit_rect(0, 0, 10, 10));
407 planner.add(blit_rect(50, 50, 10, 10)); assert!(planner.overflowed());
410
411 let mut list = InvalidationList::<8>::with_size(100, 100);
412 ingest_blit_planner(&planner, &mut list);
413
414 assert_eq!(list.plan(), PresentPlan::FullFrame);
415 }
416
417 #[test]
418 fn non_overflowed_planner_does_not_promote_to_full_frame() {
419 let mut planner: BlitPlanner<4> = BlitPlanner::new();
420 planner.add(blit_rect(1, 1, 3, 3));
421
422 let mut list = InvalidationList::<8>::with_size(100, 100);
423 ingest_blit_planner(&planner, &mut list);
424
425 assert!(!list.is_full_frame());
426 }
427
428 #[test]
434 fn end_to_end_invalidation_list_to_buffer_display() {
435 let (w, h) = (20usize, 20usize);
436 let mut frame = solid_frame(w, h, green());
438 for py in 0..10usize {
439 for px in 0..10usize {
440 frame[py * w + px] = red();
441 }
442 }
443
444 let mut list = InvalidationList::<8>::with_size(w as i32, h as i32);
446 list.push(r(0, 0, 10, 10));
447 let plan = list.plan();
448
449 let mut disp = BufferDisplay::new(w, h);
450 let count = present_plan(&mut disp, plan, &frame);
451
452 assert_eq!(count, 1);
453 for py in 0..10usize {
455 for px in 0..10usize {
456 assert_eq!(disp.buffer[py * w + px], red(), "({px},{py}) should be red");
457 }
458 }
459 assert_eq!(disp.buffer[10 * w + 10], black());
461 }
462
463 #[test]
464 fn end_to_end_with_buffered_invalidation() {
465 let (w, h) = (16usize, 16usize);
466 let frame = solid_frame(w, h, blue());
467
468 let mut buf = BufferedInvalidation::<8, 2>::with_size(w as i32, h as i32);
470 buf.push(r(0, 0, 4, 4));
471
472 let plan = buf.plan();
474 let mut disp = BufferDisplay::new(w, h);
475 let count = present_plan(&mut disp, plan, &frame);
476 assert_eq!(count, 1);
477 buf.finish_present();
478
479 let plan2 = buf.plan();
481 assert!(matches!(plan2, PresentPlan::Rects(_)));
482 present_plan(&mut disp, plan2, &frame);
483 buf.finish_present();
484
485 assert_eq!(buf.plan(), PresentPlan::None);
487 }
488
489 #[test]
490 fn full_frame_plan_on_overflowed_list_flushes_whole_screen() {
491 let (w, h) = (8usize, 8usize);
492 let frame = solid_frame(w, h, green());
493 let mut disp = BufferDisplay::new(w, h);
494
495 let mut list = InvalidationList::<1>::with_size(w as i32, h as i32);
497 list.push(r(0, 0, 2, 2));
498 list.push(r(4, 4, 2, 2)); assert_eq!(list.plan(), PresentPlan::FullFrame);
501 let count = present_plan(&mut disp, list.plan(), &frame);
502 assert_eq!(count, 1);
503 assert!(disp.buffer.iter().all(|&c| c == green()));
504 }
505}