1use core::{convert::Infallible, marker::PhantomData};
10
11use crate::{Color, FrameBuffer, FrameBufferOperations, MutableFrameBuffer, WordSize};
12use embedded_dma::ReadBuffer;
13use embedded_graphics::prelude::{DrawTarget, OriginDimensions, PixelColor, Point, Size};
14
15#[must_use]
26pub const fn compute_tiled_cols(
27 cols: usize,
28 num_panels_wide: usize,
29 num_panels_high: usize,
30) -> usize {
31 cols * num_panels_wide * num_panels_high
32}
33
34pub trait PixelRemapper {
46 const VIRT_ROWS: usize;
48 const VIRT_COLS: usize;
50 const FB_ROWS: usize;
52 const FB_COLS: usize;
54
55 #[inline]
57 fn remap<C: PixelColor>(mut pixel: embedded_graphics::Pixel<C>) -> embedded_graphics::Pixel<C> {
58 pixel.0 = Self::remap_point(pixel.0);
59 pixel
60 }
61
62 #[inline]
64 #[must_use]
65 fn remap_point(mut point: Point) -> Point {
66 if point.x < 0 || point.y < 0 {
67 return point;
69 }
70 let (re_x, re_y) = Self::remap_xy(point.x as usize, point.y as usize);
71 point.x = i32::from(re_x as u16);
73 point.y = i32::from(re_y as u16);
74 point
75 }
76
77 fn remap_xy(x: usize, y: usize) -> (usize, usize);
79
80 #[inline]
82 #[must_use]
83 fn virtual_size() -> (usize, usize) {
84 (Self::VIRT_ROWS, Self::VIRT_COLS)
85 }
86
87 #[inline]
89 #[must_use]
90 fn fb_size() -> (usize, usize) {
91 (Self::FB_ROWS, Self::FB_COLS)
92 }
93}
94
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
112#[derive(core::fmt::Debug)]
113pub struct ChainTopRightDown<
114 const PANEL_ROWS: usize,
115 const PANEL_COLS: usize,
116 const TILE_ROWS: usize,
117 const TILE_COLS: usize,
118> {}
119
120impl<
121 const PANEL_ROWS: usize,
122 const PANEL_COLS: usize,
123 const TILE_ROWS: usize,
124 const TILE_COLS: usize,
125 > PixelRemapper for ChainTopRightDown<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
126{
127 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
128 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
129 const FB_ROWS: usize = PANEL_ROWS;
130 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
131
132 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
133 let row = y / PANEL_ROWS;
135 let base = (TILE_ROWS - 1 - row) * Self::VIRT_COLS;
136
137 if row % 2 == 1 {
138 (
140 base + Self::VIRT_COLS - 1 - x, PANEL_ROWS - 1 - (y % PANEL_ROWS), )
143 } else {
144 (base + x, y % PANEL_ROWS) }
146 }
147}
148
149#[cfg_attr(feature = "defmt", derive(defmt::Format))]
200#[derive(core::fmt::Debug)]
201pub struct TiledFrameBuffer<
202 F,
203 M: PixelRemapper,
204 const PANEL_ROWS: usize,
205 const PANEL_COLS: usize,
206 const NROWS: usize,
207 const BITS: u8,
208 const FRAME_COUNT: usize,
209 const TILE_ROWS: usize,
210 const TILE_COLS: usize,
211 const FB_COLS: usize,
212>(F, PhantomData<M>);
213
214impl<
215 F: Default,
216 M: PixelRemapper,
217 const PANEL_ROWS: usize,
218 const PANEL_COLS: usize,
219 const NROWS: usize,
220 const BITS: u8,
221 const FRAME_COUNT: usize,
222 const TILE_ROWS: usize,
223 const TILE_COLS: usize,
224 const FB_COLS: usize,
225 >
226 TiledFrameBuffer<
227 F,
228 M,
229 PANEL_ROWS,
230 PANEL_COLS,
231 NROWS,
232 BITS,
233 FRAME_COUNT,
234 TILE_ROWS,
235 TILE_COLS,
236 FB_COLS,
237 >
238{
239 #[must_use]
243 pub fn new() -> Self {
244 Self(F::default(), PhantomData)
245 }
246}
247
248impl<
249 F: Default,
250 M: PixelRemapper,
251 const PANEL_ROWS: usize,
252 const PANEL_COLS: usize,
253 const NROWS: usize,
254 const BITS: u8,
255 const FRAME_COUNT: usize,
256 const TILE_ROWS: usize,
257 const TILE_COLS: usize,
258 const FB_COLS: usize,
259 > Default
260 for TiledFrameBuffer<
261 F,
262 M,
263 PANEL_ROWS,
264 PANEL_COLS,
265 NROWS,
266 BITS,
267 FRAME_COUNT,
268 TILE_ROWS,
269 TILE_COLS,
270 FB_COLS,
271 >
272{
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl<
279 F: DrawTarget<Error = Infallible, Color = Color>,
280 M: PixelRemapper,
281 const PANEL_ROWS: usize,
282 const PANEL_COLS: usize,
283 const NROWS: usize,
284 const BITS: u8,
285 const FRAME_COUNT: usize,
286 const TILE_ROWS: usize,
287 const TILE_COLS: usize,
288 const FB_COLS: usize,
289 > DrawTarget
290 for TiledFrameBuffer<
291 F,
292 M,
293 PANEL_ROWS,
294 PANEL_COLS,
295 NROWS,
296 BITS,
297 FRAME_COUNT,
298 TILE_ROWS,
299 TILE_COLS,
300 FB_COLS,
301 >
302{
303 type Color = Color;
304 type Error = Infallible;
305
306 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
307 where
308 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
309 {
310 self.0.draw_iter(pixels.into_iter().map(M::remap))
311 }
312}
313
314impl<
315 F: DrawTarget<Error = Infallible, Color = Color>,
316 M: PixelRemapper,
317 const PANEL_ROWS: usize,
318 const PANEL_COLS: usize,
319 const NROWS: usize,
320 const BITS: u8,
321 const FRAME_COUNT: usize,
322 const TILE_ROWS: usize,
323 const TILE_COLS: usize,
324 const FB_COLS: usize,
325 > OriginDimensions
326 for TiledFrameBuffer<
327 F,
328 M,
329 PANEL_ROWS,
330 PANEL_COLS,
331 NROWS,
332 BITS,
333 FRAME_COUNT,
334 TILE_ROWS,
335 TILE_COLS,
336 FB_COLS,
337 >
338{
339 fn size(&self) -> Size {
340 Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
341 }
342}
343
344impl<
345 F: FrameBufferOperations + FrameBuffer,
346 M: PixelRemapper,
347 const PANEL_ROWS: usize,
348 const PANEL_COLS: usize,
349 const NROWS: usize,
350 const BITS: u8,
351 const FRAME_COUNT: usize,
352 const TILE_ROWS: usize,
353 const TILE_COLS: usize,
354 const FB_COLS: usize,
355 > FrameBufferOperations
356 for TiledFrameBuffer<
357 F,
358 M,
359 PANEL_ROWS,
360 PANEL_COLS,
361 NROWS,
362 BITS,
363 FRAME_COUNT,
364 TILE_ROWS,
365 TILE_COLS,
366 FB_COLS,
367 >
368{
369 #[inline]
370 fn erase(&mut self) {
371 self.0.erase();
372 }
373
374 #[inline]
375 fn set_pixel(&mut self, p: Point, color: Color) {
376 self.0.set_pixel(M::remap_point(p), color);
377 }
378}
379
380unsafe impl<
381 T,
382 F: ReadBuffer<Word = T>,
383 M: PixelRemapper,
384 const PANEL_ROWS: usize,
385 const PANEL_COLS: usize,
386 const NROWS: usize,
387 const BITS: u8,
388 const FRAME_COUNT: usize,
389 const TILE_ROWS: usize,
390 const TILE_COLS: usize,
391 const FB_COLS: usize,
392 > ReadBuffer
393 for TiledFrameBuffer<
394 F,
395 M,
396 PANEL_ROWS,
397 PANEL_COLS,
398 NROWS,
399 BITS,
400 FRAME_COUNT,
401 TILE_ROWS,
402 TILE_COLS,
403 FB_COLS,
404 >
405{
406 type Word = T;
407
408 unsafe fn read_buffer(&self) -> (*const T, usize) {
409 self.0.read_buffer()
410 }
411}
412
413impl<
414 F: FrameBuffer,
415 M: PixelRemapper,
416 const PANEL_ROWS: usize,
417 const PANEL_COLS: usize,
418 const NROWS: usize,
419 const BITS: u8,
420 const FRAME_COUNT: usize,
421 const TILE_ROWS: usize,
422 const TILE_COLS: usize,
423 const FB_COLS: usize,
424 > FrameBuffer
425 for TiledFrameBuffer<
426 F,
427 M,
428 PANEL_ROWS,
429 PANEL_COLS,
430 NROWS,
431 BITS,
432 FRAME_COUNT,
433 TILE_ROWS,
434 TILE_COLS,
435 FB_COLS,
436 >
437{
438 fn get_word_size(&self) -> WordSize {
439 self.0.get_word_size()
440 }
441
442 fn plane_count(&self) -> usize {
443 self.0.plane_count()
444 }
445
446 fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
447 self.0.plane_ptr_len(plane_idx)
448 }
449}
450
451impl<
452 F: MutableFrameBuffer,
453 M: PixelRemapper,
454 const PANEL_ROWS: usize,
455 const PANEL_COLS: usize,
456 const NROWS: usize,
457 const BITS: u8,
458 const FRAME_COUNT: usize,
459 const TILE_ROWS: usize,
460 const TILE_COLS: usize,
461 const FB_COLS: usize,
462 > MutableFrameBuffer
463 for TiledFrameBuffer<
464 F,
465 M,
466 PANEL_ROWS,
467 PANEL_COLS,
468 NROWS,
469 BITS,
470 FRAME_COUNT,
471 TILE_ROWS,
472 TILE_COLS,
473 FB_COLS,
474 >
475{
476}
477
478#[cfg(test)]
479mod tests {
480 extern crate std;
481
482 use embedded_graphics::prelude::*;
483
484 use super::*;
485 use crate::MutableFrameBuffer;
486 use core::convert::Infallible;
487
488 #[test]
489 fn test_virtual_size_function_with_equal_rows_and_cols() {
490 const ROWS_IN_PANEL: usize = 32;
491 const COLS_IN_PANEL: usize = 64;
492 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
493 let virt_size = PanelChain::virtual_size();
494 assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL * 3));
495 }
496
497 #[test]
498 fn test_virtual_size_function_with_uneven_rows_and_cols() {
499 const ROWS_IN_PANEL: usize = 32;
500 const COLS_IN_PANEL: usize = 64;
501 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
502 let virt_size = PanelChain::virtual_size();
503 assert_eq!(virt_size, (ROWS_IN_PANEL * 5, COLS_IN_PANEL * 3));
504 }
505
506 #[test]
507 fn test_virtual_size_function_with_single_column() {
508 const ROWS_IN_PANEL: usize = 32;
509 const COLS_IN_PANEL: usize = 64;
510 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
511 let virt_size = PanelChain::virtual_size();
512 assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL));
513 }
514
515 #[test]
516 fn test_fb_size_function_with_equal_rows_and_cols() {
517 const ROWS_IN_PANEL: usize = 32;
518 const COLS_IN_PANEL: usize = 64;
519 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
520 let virt_size = PanelChain::fb_size();
521 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 9));
522 }
523
524 #[test]
525 fn test_fb_size_function_with_uneven_rows_and_cols() {
526 const ROWS_IN_PANEL: usize = 32;
527 const COLS_IN_PANEL: usize = 64;
528 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
529 let virt_size = PanelChain::fb_size();
530 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 15));
531 }
532
533 #[test]
534 fn test_fb_size_function_with_single_column() {
535 const ROWS_IN_PANEL: usize = 32;
536 const COLS_IN_PANEL: usize = 64;
537 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
538 let virt_size = PanelChain::fb_size();
539 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 3));
540 }
541
542 #[test]
543 fn test_pixel_remap_top_right_down_point_in_origin() {
544 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
545
546 let pixel = PanelChain::remap(Pixel(Point::new(0, 0), Color::RED));
547 assert_eq!(pixel.0, Point::new(384, 0));
548 }
549
550 #[test]
551 fn test_pixel_remap_top_right_down_point_in_bottom_left_corner() {
552 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
553
554 let pixel = PanelChain::remap(Pixel(Point::new(0, 95), Color::RED));
555 assert_eq!(pixel.0, Point::new(0, 31));
556 }
557
558 #[test]
559 fn test_pixel_remap_top_right_down_point_in_bottom_right_corner() {
560 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
561
562 let pixel = PanelChain::remap(Pixel(Point::new(191, 95), Color::RED));
563 assert_eq!(pixel.0, Point::new(191, 31));
564 }
565
566 #[test]
567 fn test_pixel_remap_top_right_down_point_on_x_right_edge_of_first_panel() {
568 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
569
570 let pixel = PanelChain::remap(Pixel(Point::new(63, 0), Color::RED));
571 assert_eq!(pixel.0, Point::new(447, 0));
572 }
573
574 #[test]
575 fn test_pixel_remap_top_right_down_point_on_x_left_edge_of_second_panel() {
576 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
577
578 let pixel = PanelChain::remap(Pixel(Point::new(64, 0), Color::RED));
579 assert_eq!(pixel.0, Point::new(448, 0));
580 }
581
582 #[test]
583 fn test_pixel_remap_top_right_down_point_on_y_bottom_edge_of_first_panel() {
584 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
585
586 let pixel = PanelChain::remap(Pixel(Point::new(0, 31), Color::RED));
587 assert_eq!(pixel.0, Point::new(384, 31));
588 }
589
590 #[test]
591 fn test_pixel_remap_top_right_down_point_on_y_top_edge_of_fourth_panel() {
592 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
593
594 let pixel = PanelChain::remap(Pixel(Point::new(0, 32), Color::RED));
595 assert_eq!(pixel.0, Point::new(383, 31));
596 }
597
598 #[test]
599 fn test_pixel_remap_top_right_down_point_slightly_to_the_top_middle() {
600 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
601
602 let pixel = PanelChain::remap(Pixel(Point::new(100, 40), Color::RED));
603 assert_eq!(pixel.0, Point::new(283, 23));
604 }
605
606 #[test]
607 fn test_pixel_remap_negative_pixel_does_not_remap() {
608 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
609
610 let pixel = PanelChain::remap(Pixel(Point::new(-5, 40), Color::RED));
611 assert_eq!(pixel.0, Point::new(-5, 40));
612 }
613
614 #[test]
615 fn test_compute_tiled_cols() {
616 assert_eq!(192, compute_tiled_cols(32, 3, 2));
617 }
618
619 #[test]
620 fn test_tiling_framebuffer_canvas_size() {
621 use crate::plain::DmaFrameBuffer;
622 use crate::tiling::{compute_tiled_cols, ChainTopRightDown, TiledFrameBuffer};
623 use crate::{compute_frame_count, compute_rows};
624
625 const TILED_COLS: usize = 3;
626 const TILED_ROWS: usize = 3;
627 const ROWS: usize = 32;
628 const PANEL_COLS: usize = 64;
629 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
630 const BITS: u8 = 2;
631 const NROWS: usize = compute_rows(ROWS);
632 const FRAME_COUNT: usize = compute_frame_count(BITS);
633
634 type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
635 type TiledFBType = TiledFrameBuffer<
636 FBType,
637 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
638 ROWS,
639 PANEL_COLS,
640 NROWS,
641 BITS,
642 FRAME_COUNT,
643 TILED_ROWS,
644 TILED_COLS,
645 FB_COLS,
646 >;
647
648 let fb = TiledFBType::new();
649
650 assert_eq!(fb.size(), Size::new(192, 96));
651 }
652
653 struct TestFrameBuffer {
655 calls: std::cell::RefCell<std::vec::Vec<Call>>,
656 buf: [u8; 8],
657 word_size: WordSize,
658 }
659
660 #[derive(Debug, Clone, PartialEq, Eq)]
661 enum Call {
662 Erase,
663 SetPixel { p: Point, color: Color },
664 Draw(std::vec::Vec<(Point, Color)>),
665 }
666
667 impl TestFrameBuffer {
668 fn new(word_size: WordSize) -> Self {
669 Self {
670 calls: std::cell::RefCell::new(std::vec::Vec::new()),
671 buf: [0; 8],
672 word_size,
673 }
674 }
675
676 fn take_calls(&self) -> std::vec::Vec<Call> {
677 core::mem::take(&mut *self.calls.borrow_mut())
678 }
679 }
680
681 impl Default for TestFrameBuffer {
682 fn default() -> Self {
683 Self::new(WordSize::Eight)
684 }
685 }
686
687 impl DrawTarget for TestFrameBuffer {
688 type Color = Color;
689 type Error = Infallible;
690
691 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
692 where
693 I: IntoIterator<Item = Pixel<Self::Color>>,
694 {
695 let v = pixels.into_iter().map(|p| (p.0, p.1)).collect();
696 self.calls.borrow_mut().push(Call::Draw(v));
697 Ok(())
698 }
699 }
700
701 impl OriginDimensions for TestFrameBuffer {
702 fn size(&self) -> Size {
703 Size::new(1, 1)
704 }
705 }
706
707 impl FrameBuffer for TestFrameBuffer {
708 fn get_word_size(&self) -> WordSize {
709 self.word_size
710 }
711
712 fn plane_count(&self) -> usize {
713 1
714 }
715
716 fn plane_ptr_len(&self, _plane_idx: usize) -> (*const u8, usize) {
717 (self.buf.as_ptr(), self.buf.len())
718 }
719 }
720
721 impl FrameBufferOperations for TestFrameBuffer {
722 fn erase(&mut self) {
723 self.calls.borrow_mut().push(Call::Erase);
724 }
725
726 fn set_pixel(&mut self, p: Point, color: Color) {
727 self.calls.borrow_mut().push(Call::SetPixel { p, color });
728 }
729 }
730
731 impl MutableFrameBuffer for TestFrameBuffer {}
732
733 unsafe impl embedded_dma::ReadBuffer for TestFrameBuffer {
734 type Word = u8;
735
736 unsafe fn read_buffer(&self) -> (*const u8, usize) {
737 (self.buf.as_ptr(), self.buf.len())
738 }
739 }
740
741 #[test]
742 fn test_tiled_draw_iter_forwards_with_remap() {
743 const TILED_COLS: usize = 3;
744 const TILED_ROWS: usize = 3;
745 const ROWS: usize = 32;
746 const PANEL_COLS: usize = 64;
747 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
748
749 let mut fb = TiledFrameBuffer::<
750 TestFrameBuffer,
751 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
752 ROWS,
753 PANEL_COLS,
754 { crate::compute_rows(ROWS) },
755 2,
756 { crate::compute_frame_count(2) },
757 TILED_ROWS,
758 TILED_COLS,
759 FB_COLS,
760 >(
761 TestFrameBuffer::new(WordSize::Eight),
762 core::marker::PhantomData,
763 );
764
765 let input = [
766 Pixel(Point::new(0, 0), Color::RED),
767 Pixel(Point::new(63, 0), Color::GREEN),
768 Pixel(Point::new(64, 0), Color::BLUE),
769 Pixel(Point::new(100, 40), Color::WHITE),
770 ];
771
772 fb.draw_iter(input.into_iter()).unwrap();
773
774 let calls = fb.0.take_calls();
775 assert_eq!(calls.len(), 1);
776 match &calls[0] {
777 Call::Draw(v) => {
778 let expected =
779 [
780 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
781 Pixel(Point::new(0, 0), Color::RED),
782 ),
783 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
784 Pixel(Point::new(63, 0), Color::GREEN),
785 ),
786 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
787 Pixel(Point::new(64, 0), Color::BLUE),
788 ),
789 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
790 Pixel(Point::new(100, 40), Color::WHITE),
791 ),
792 ];
793 let expected_points: std::vec::Vec<(Point, Color)> =
794 expected.iter().map(|p| (p.0, p.1)).collect();
795 assert_eq!(v.as_slice(), expected_points.as_slice());
796 }
797 _ => panic!("expected a Draw call"),
798 }
799 }
800
801 #[test]
802 fn test_tiled_set_pixel_remaps_and_forwards() {
803 const TILED_COLS: usize = 3;
804 const TILED_ROWS: usize = 3;
805 const ROWS: usize = 32;
806 const PANEL_COLS: usize = 64;
807 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
808
809 let mut fb = TiledFrameBuffer::<
810 TestFrameBuffer,
811 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
812 ROWS,
813 PANEL_COLS,
814 { crate::compute_rows(ROWS) },
815 2,
816 { crate::compute_frame_count(2) },
817 TILED_ROWS,
818 TILED_COLS,
819 FB_COLS,
820 >(
821 TestFrameBuffer::new(WordSize::Eight),
822 core::marker::PhantomData,
823 );
824
825 let p = Point::new(100, 40);
826 fb.set_pixel(p, Color::BLUE);
827
828 let calls = fb.0.take_calls();
829 assert_eq!(calls.len(), 1);
830 match calls.into_iter().next().unwrap() {
831 Call::SetPixel { p: rp, color } => {
832 let expected =
833 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap_point(p);
834 assert_eq!(rp, expected);
835 assert_eq!(color, Color::BLUE);
836 }
837 _ => panic!("expected a SetPixel call"),
838 }
839 }
840
841 #[test]
842 fn test_tiled_erase_forwards() {
843 const TILED_COLS: usize = 2;
844 const TILED_ROWS: usize = 2;
845 const ROWS: usize = 32;
846 const PANEL_COLS: usize = 64;
847 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
848
849 let mut fb = TiledFrameBuffer::<
850 TestFrameBuffer,
851 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
852 ROWS,
853 PANEL_COLS,
854 { crate::compute_rows(ROWS) },
855 2,
856 { crate::compute_frame_count(2) },
857 TILED_ROWS,
858 TILED_COLS,
859 FB_COLS,
860 >(
861 TestFrameBuffer::new(WordSize::Eight),
862 core::marker::PhantomData,
863 );
864 fb.erase();
865 let calls = fb.0.take_calls();
866 assert_eq!(calls, std::vec![Call::Erase]);
867 }
868
869 #[test]
870 fn test_tiled_negative_coordinates_not_remapped() {
871 const TILED_COLS: usize = 2;
872 const TILED_ROWS: usize = 2;
873 const ROWS: usize = 32;
874 const PANEL_COLS: usize = 64;
875 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
876
877 let mut fb = TiledFrameBuffer::<
878 TestFrameBuffer,
879 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
880 ROWS,
881 PANEL_COLS,
882 { crate::compute_rows(ROWS) },
883 2,
884 { crate::compute_frame_count(2) },
885 TILED_ROWS,
886 TILED_COLS,
887 FB_COLS,
888 >(
889 TestFrameBuffer::new(WordSize::Eight),
890 core::marker::PhantomData,
891 );
892
893 let neg = Point::new(-3, 5);
895 fb.set_pixel(neg, Color::GREEN);
896 fb.draw_iter(core::iter::once(Pixel(Point::new(10, -2), Color::RED)))
898 .unwrap();
899
900 let calls = fb.0.take_calls();
901 assert_eq!(calls.len(), 2);
902 assert!(matches!(calls[0], Call::SetPixel { p, .. } if p == neg));
903 match &calls[1] {
904 Call::Draw(v) => {
905 assert_eq!(v.as_slice(), &[(Point::new(10, -2), Color::RED)]);
906 }
907 _ => panic!("expected a Draw call"),
908 }
909 }
910
911 #[test]
912 fn test_tiled_read_buffer_passthrough() {
913 const TILED_COLS: usize = 2;
914 const TILED_ROWS: usize = 2;
915 const ROWS: usize = 32;
916 const PANEL_COLS: usize = 64;
917 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
918
919 let fb = TiledFrameBuffer::<
920 TestFrameBuffer,
921 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
922 ROWS,
923 PANEL_COLS,
924 { crate::compute_rows(ROWS) },
925 2,
926 { crate::compute_frame_count(2) },
927 TILED_ROWS,
928 TILED_COLS,
929 FB_COLS,
930 >(
931 TestFrameBuffer::new(WordSize::Eight),
932 core::marker::PhantomData,
933 );
934
935 let inner_ptr = fb.0.buf.as_ptr();
936 let inner_len = fb.0.buf.len();
937
938 let (ptr, len) = unsafe { fb.read_buffer() };
939 assert_eq!(ptr, inner_ptr);
940 assert_eq!(len, inner_len);
941 }
942
943 #[test]
944 fn test_tiled_get_word_size_passthrough() {
945 const TILED_COLS: usize = 2;
946 const TILED_ROWS: usize = 2;
947 const ROWS: usize = 32;
948 const PANEL_COLS: usize = 64;
949 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
950
951 let fb = TiledFrameBuffer::<
952 TestFrameBuffer,
953 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
954 ROWS,
955 PANEL_COLS,
956 { crate::compute_rows(ROWS) },
957 2,
958 { crate::compute_frame_count(2) },
959 TILED_ROWS,
960 TILED_COLS,
961 FB_COLS,
962 >(
963 TestFrameBuffer::new(WordSize::Sixteen),
964 core::marker::PhantomData,
965 );
966 assert_eq!(fb.get_word_size(), WordSize::Sixteen);
967 }
968
969 #[test]
970 fn test_tiled_get_word_size_eight_passthrough() {
971 const TILED_COLS: usize = 2;
972 const TILED_ROWS: usize = 2;
973 const ROWS: usize = 32;
974 const PANEL_COLS: usize = 64;
975 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
976
977 let fb = TiledFrameBuffer::<
978 TestFrameBuffer,
979 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
980 ROWS,
981 PANEL_COLS,
982 { crate::compute_rows(ROWS) },
983 2,
984 { crate::compute_frame_count(2) },
985 TILED_ROWS,
986 TILED_COLS,
987 FB_COLS,
988 >(
989 TestFrameBuffer::new(WordSize::Eight),
990 core::marker::PhantomData,
991 );
992 assert_eq!(fb.get_word_size(), WordSize::Eight);
993 }
994
995 struct Huge<
997 const PANEL_ROWS: usize,
998 const PANEL_COLS: usize,
999 const TILE_ROWS: usize,
1000 const TILE_COLS: usize,
1001 >;
1002
1003 impl<
1004 const PANEL_ROWS: usize,
1005 const PANEL_COLS: usize,
1006 const TILE_ROWS: usize,
1007 const TILE_COLS: usize,
1008 > PixelRemapper for Huge<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
1009 {
1010 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
1011 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
1012 const FB_ROWS: usize = PANEL_ROWS;
1013 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
1014
1015 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
1016 (x + 70_000, y + 70_000)
1017 }
1018 }
1019
1020 #[test]
1021 fn test_remap_point_truncates_to_u16_range() {
1022 const TILED_COLS: usize = 1;
1023 const TILED_ROWS: usize = 1;
1024 const ROWS: usize = 32;
1025 const PANEL_COLS: usize = 64;
1026 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1027
1028 let mut fb = TiledFrameBuffer::<
1029 TestFrameBuffer,
1030 Huge<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1031 ROWS,
1032 PANEL_COLS,
1033 { crate::compute_rows(ROWS) },
1034 2,
1035 { crate::compute_frame_count(2) },
1036 TILED_ROWS,
1037 TILED_COLS,
1038 FB_COLS,
1039 >(
1040 TestFrameBuffer::new(WordSize::Eight),
1041 core::marker::PhantomData,
1042 );
1043 fb.set_pixel(Point::new(1, 2), Color::RED);
1044
1045 let calls = fb.0.take_calls();
1046 match calls.into_iter().next().unwrap() {
1047 Call::SetPixel { p, color } => {
1048 let (rx, ry) = (1usize + 70_000, 2usize + 70_000);
1049 let expected = Point::new(i32::from(rx as u16), i32::from(ry as u16));
1050 assert_eq!(p, expected);
1051 assert_eq!(color, Color::RED);
1052 }
1053 other => panic!("unexpected call recorded: {other:?}"),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_more_compute_tiled_cols_cases() {
1059 assert_eq!(compute_tiled_cols(64, 1, 4), 256);
1060 assert_eq!(compute_tiled_cols(64, 4, 1), 256);
1061 assert_eq!(compute_tiled_cols(32, 4, 5), 640);
1062 }
1063
1064 #[test]
1065 fn test_tiled_default_and_new_construct() {
1066 const TILED_COLS: usize = 4;
1067 const TILED_ROWS: usize = 2;
1068 const ROWS: usize = 32;
1069 const PANEL_COLS: usize = 64;
1070 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1071
1072 let fb_default = TiledFrameBuffer::<
1073 TestFrameBuffer,
1074 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1075 ROWS,
1076 PANEL_COLS,
1077 { crate::compute_rows(ROWS) },
1078 2,
1079 { crate::compute_frame_count(2) },
1080 TILED_ROWS,
1081 TILED_COLS,
1082 FB_COLS,
1083 >::default();
1084
1085 let fb_new = TiledFrameBuffer::<
1086 TestFrameBuffer,
1087 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1088 ROWS,
1089 PANEL_COLS,
1090 { crate::compute_rows(ROWS) },
1091 2,
1092 { crate::compute_frame_count(2) },
1093 TILED_ROWS,
1094 TILED_COLS,
1095 FB_COLS,
1096 >::new();
1097
1098 assert_eq!(fb_default.get_word_size(), WordSize::Eight);
1100 assert_eq!(fb_new.get_word_size(), WordSize::Eight);
1101
1102 let expected_size = Size::new((PANEL_COLS * TILED_COLS) as u32, (ROWS * TILED_ROWS) as u32);
1104 assert_eq!(fb_default.size(), expected_size);
1105 assert_eq!(fb_new.size(), expected_size);
1106
1107 assert!(fb_default.0.take_calls().is_empty());
1109 assert!(fb_new.0.take_calls().is_empty());
1110 }
1111
1112 #[test]
1113 fn test_tiled_origin_dimensions_matches_virtual_size() {
1114 const TILED_COLS: usize = 5;
1115 const TILED_ROWS: usize = 2;
1116 const ROWS: usize = 32;
1117 const PANEL_COLS: usize = 64;
1118 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1119
1120 let fb = TiledFrameBuffer::<
1121 TestFrameBuffer,
1122 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1123 ROWS,
1124 PANEL_COLS,
1125 { crate::compute_rows(ROWS) },
1126 2,
1127 { crate::compute_frame_count(2) },
1128 TILED_ROWS,
1129 TILED_COLS,
1130 FB_COLS,
1131 >::new();
1132
1133 let (virt_rows, virt_cols) = <ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS> as PixelRemapper>::virtual_size();
1134 assert_eq!(fb.size(), Size::new(virt_cols as u32, virt_rows as u32));
1135 }
1136
1137 fn expected_ctrdd_xy<const PR: usize, const PC: usize, const TR: usize, const TC: usize>(
1139 x: usize,
1140 y: usize,
1141 ) -> (usize, usize) {
1142 let row = y / PR;
1143 let base = (TR - 1 - row) * (PC * TC);
1144 if row % 2 == 1 {
1145 (base + (PC * TC) - 1 - x, PR - 1 - (y % PR))
1146 } else {
1147 (base + x, y % PR)
1148 }
1149 }
1150
1151 #[test]
1152 fn test_chain_top_right_down_corners_2x3() {
1153 const PR: usize = 32;
1154 const PC: usize = 64;
1155 const TR: usize = 2;
1156 const TC: usize = 3;
1157 type M = ChainTopRightDown<PR, PC, TR, TC>;
1158
1159 for r in 0..TR {
1160 for c in 0..TC {
1161 let x0 = c * PC;
1162 let y0 = r * PR;
1163 let corners = [
1164 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1169
1170 for &(x, y) in &corners {
1171 let got = <M as PixelRemapper>::remap_xy(x, y);
1172 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1173 assert_eq!(
1174 got, exp,
1175 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1176 r, c, x, y
1177 );
1178 }
1179 }
1180 }
1181 }
1182
1183 #[test]
1184 fn test_chain_top_right_down_corners_3x2() {
1185 const PR: usize = 32;
1186 const PC: usize = 64;
1187 const TR: usize = 3;
1188 const TC: usize = 2;
1189 type M = ChainTopRightDown<PR, PC, TR, TC>;
1190
1191 for r in 0..TR {
1192 for c in 0..TC {
1193 let x0 = c * PC;
1194 let y0 = r * PR;
1195 let corners = [
1196 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1201
1202 for &(x, y) in &corners {
1203 let got = <M as PixelRemapper>::remap_xy(x, y);
1204 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1205 assert_eq!(
1206 got, exp,
1207 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1208 r, c, x, y
1209 );
1210 }
1211 }
1212 }
1213 }
1214}