1use core::{convert::Infallible, marker::PhantomData};
17
18use crate::{
19 BcmLenReps, Color, FrameBuffer, FrameBufferOperations, MutableFrameBuffer,
20 BCM_SEQUENCE_CAPACITY,
21};
22use embedded_graphics::prelude::{DrawTarget, OriginDimensions, PixelColor, Point, Size};
23
24#[must_use]
35pub const fn compute_tiled_cols(
36 cols: usize,
37 num_panels_wide: usize,
38 num_panels_high: usize,
39) -> usize {
40 cols * num_panels_wide * num_panels_high
41}
42
43pub trait PixelRemapper {
55 const VIRT_ROWS: usize;
57 const VIRT_COLS: usize;
59 const FB_ROWS: usize;
61 const FB_COLS: usize;
63
64 #[inline]
66 fn remap<C: PixelColor>(mut pixel: embedded_graphics::Pixel<C>) -> embedded_graphics::Pixel<C> {
67 pixel.0 = Self::remap_point(pixel.0);
68 pixel
69 }
70
71 #[inline]
73 #[must_use]
74 fn remap_point(mut point: Point) -> Point {
75 if point.x < 0 || point.y < 0 {
76 return point;
78 }
79 let (re_x, re_y) = Self::remap_xy(point.x as usize, point.y as usize);
80 point.x = i32::from(re_x as u16);
82 point.y = i32::from(re_y as u16);
83 point
84 }
85
86 fn remap_xy(x: usize, y: usize) -> (usize, usize);
88
89 #[inline]
91 #[must_use]
92 fn virtual_size() -> (usize, usize) {
93 (Self::VIRT_ROWS, Self::VIRT_COLS)
94 }
95
96 #[inline]
98 #[must_use]
99 fn fb_size() -> (usize, usize) {
100 (Self::FB_ROWS, Self::FB_COLS)
101 }
102}
103
104#[cfg_attr(feature = "defmt", derive(defmt::Format))]
121#[derive(core::fmt::Debug)]
122pub struct ChainTopRightDown<
123 const PANEL_ROWS: usize,
124 const PANEL_COLS: usize,
125 const TILE_ROWS: usize,
126 const TILE_COLS: usize,
127> {}
128
129impl<
130 const PANEL_ROWS: usize,
131 const PANEL_COLS: usize,
132 const TILE_ROWS: usize,
133 const TILE_COLS: usize,
134 > PixelRemapper for ChainTopRightDown<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
135{
136 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
137 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
138 const FB_ROWS: usize = PANEL_ROWS;
139 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
140
141 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
142 let row = y / PANEL_ROWS;
144 let base = (TILE_ROWS - 1 - row) * Self::VIRT_COLS;
145
146 if row % 2 == 1 {
147 (
149 base + Self::VIRT_COLS - 1 - x, PANEL_ROWS - 1 - (y % PANEL_ROWS), )
152 } else {
153 (base + x, y % PANEL_ROWS) }
155 }
156}
157
158pub mod quarter_scan {
171 pub trait Variant {
179 const SLOT: [(usize, usize); 4];
181 }
182
183 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
185 #[derive(core::fmt::Debug)]
186 pub struct Linear;
187
188 impl Variant for Linear {
189 const SLOT: [(usize, usize); 4] = [(0, 0), (0, 1), (1, 0), (1, 1)];
190 }
191
192 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
195 #[derive(core::fmt::Debug)]
196 pub struct SectionsSwapped;
197
198 impl Variant for SectionsSwapped {
199 const SLOT: [(usize, usize); 4] = [(0, 1), (0, 0), (1, 1), (1, 0)];
200 }
201
202 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
205 #[derive(core::fmt::Debug)]
206 pub struct HalvesSwapped;
207
208 impl Variant for HalvesSwapped {
209 const SLOT: [(usize, usize); 4] = [(1, 0), (1, 1), (0, 0), (0, 1)];
210 }
211
212 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
215 #[derive(core::fmt::Debug)]
216 pub struct Alternating;
217
218 impl Variant for Alternating {
219 const SLOT: [(usize, usize); 4] = [(0, 0), (1, 0), (0, 1), (1, 1)];
220 }
221}
222
223#[derive(core::fmt::Debug)]
266pub struct QuarterScan<
267 const PANEL_ROWS: usize,
268 const PANEL_COLS: usize,
269 V: quarter_scan::Variant = quarter_scan::SectionsSwapped,
270> {
271 _variant: PhantomData<V>,
272}
273
274#[cfg(feature = "defmt")]
275impl<const PANEL_ROWS: usize, const PANEL_COLS: usize, V: quarter_scan::Variant> defmt::Format
276 for QuarterScan<PANEL_ROWS, PANEL_COLS, V>
277{
278 fn format(&self, fmt: defmt::Formatter) {
279 defmt::write!(fmt, "QuarterScan");
280 }
281}
282
283impl<const PANEL_ROWS: usize, const PANEL_COLS: usize, V: quarter_scan::Variant> PixelRemapper
284 for QuarterScan<PANEL_ROWS, PANEL_COLS, V>
285{
286 const VIRT_ROWS: usize = PANEL_ROWS;
287 const VIRT_COLS: usize = PANEL_COLS;
288 const FB_ROWS: usize = PANEL_ROWS / 2;
289 const FB_COLS: usize = PANEL_COLS * 2;
290
291 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
292 if x >= Self::VIRT_COLS || y >= Self::VIRT_ROWS {
297 return (Self::FB_COLS, Self::FB_ROWS);
298 }
299
300 let group_rows = PANEL_ROWS / 4;
304 let group = y / group_rows;
305 let addr = y % group_rows;
306 let (channel, section) = V::SLOT[group];
307 (section * PANEL_COLS + x, channel * group_rows + addr)
308 }
309}
310
311#[deprecated(
362 since = "0.11.0",
363 note = "use RemappedFrameBuffer<F, M> instead -- it works with all framebuffer types and has a simpler signature"
364)]
365#[derive(core::fmt::Debug)]
366pub struct TiledFrameBuffer<
367 F,
368 M: PixelRemapper,
369 const PANEL_ROWS: usize,
370 const PANEL_COLS: usize,
371 const NROWS: usize,
372 const BITS: u8,
373 const FRAME_COUNT: usize,
374 const TILE_ROWS: usize,
375 const TILE_COLS: usize,
376 const FB_COLS: usize,
377>(F, PhantomData<M>);
378
379#[allow(deprecated)]
380impl<
381 F: Default,
382 M: PixelRemapper,
383 const PANEL_ROWS: usize,
384 const PANEL_COLS: usize,
385 const NROWS: usize,
386 const BITS: u8,
387 const FRAME_COUNT: usize,
388 const TILE_ROWS: usize,
389 const TILE_COLS: usize,
390 const FB_COLS: usize,
391 >
392 TiledFrameBuffer<
393 F,
394 M,
395 PANEL_ROWS,
396 PANEL_COLS,
397 NROWS,
398 BITS,
399 FRAME_COUNT,
400 TILE_ROWS,
401 TILE_COLS,
402 FB_COLS,
403 >
404{
405 #[must_use]
409 pub fn new() -> Self {
410 Self(F::default(), PhantomData)
411 }
412}
413
414#[allow(deprecated)]
415impl<
416 F: Default,
417 M: PixelRemapper,
418 const PANEL_ROWS: usize,
419 const PANEL_COLS: usize,
420 const NROWS: usize,
421 const BITS: u8,
422 const FRAME_COUNT: usize,
423 const TILE_ROWS: usize,
424 const TILE_COLS: usize,
425 const FB_COLS: usize,
426 > Default
427 for TiledFrameBuffer<
428 F,
429 M,
430 PANEL_ROWS,
431 PANEL_COLS,
432 NROWS,
433 BITS,
434 FRAME_COUNT,
435 TILE_ROWS,
436 TILE_COLS,
437 FB_COLS,
438 >
439{
440 fn default() -> Self {
441 Self::new()
442 }
443}
444
445#[allow(deprecated)]
446impl<
447 F: DrawTarget<Error = Infallible, Color = Color>,
448 M: PixelRemapper,
449 const PANEL_ROWS: usize,
450 const PANEL_COLS: usize,
451 const NROWS: usize,
452 const BITS: u8,
453 const FRAME_COUNT: usize,
454 const TILE_ROWS: usize,
455 const TILE_COLS: usize,
456 const FB_COLS: usize,
457 > DrawTarget
458 for TiledFrameBuffer<
459 F,
460 M,
461 PANEL_ROWS,
462 PANEL_COLS,
463 NROWS,
464 BITS,
465 FRAME_COUNT,
466 TILE_ROWS,
467 TILE_COLS,
468 FB_COLS,
469 >
470{
471 type Color = Color;
472 type Error = Infallible;
473
474 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
475 where
476 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
477 {
478 self.0.draw_iter(pixels.into_iter().map(M::remap))
479 }
480}
481
482#[allow(deprecated)]
483impl<
484 F: DrawTarget<Error = Infallible, Color = Color>,
485 M: PixelRemapper,
486 const PANEL_ROWS: usize,
487 const PANEL_COLS: usize,
488 const NROWS: usize,
489 const BITS: u8,
490 const FRAME_COUNT: usize,
491 const TILE_ROWS: usize,
492 const TILE_COLS: usize,
493 const FB_COLS: usize,
494 > OriginDimensions
495 for TiledFrameBuffer<
496 F,
497 M,
498 PANEL_ROWS,
499 PANEL_COLS,
500 NROWS,
501 BITS,
502 FRAME_COUNT,
503 TILE_ROWS,
504 TILE_COLS,
505 FB_COLS,
506 >
507{
508 fn size(&self) -> Size {
509 Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
510 }
511}
512
513#[allow(deprecated)]
514impl<
515 F: FrameBufferOperations + FrameBuffer,
516 M: PixelRemapper,
517 const PANEL_ROWS: usize,
518 const PANEL_COLS: usize,
519 const NROWS: usize,
520 const BITS: u8,
521 const FRAME_COUNT: usize,
522 const TILE_ROWS: usize,
523 const TILE_COLS: usize,
524 const FB_COLS: usize,
525 > FrameBufferOperations
526 for TiledFrameBuffer<
527 F,
528 M,
529 PANEL_ROWS,
530 PANEL_COLS,
531 NROWS,
532 BITS,
533 FRAME_COUNT,
534 TILE_ROWS,
535 TILE_COLS,
536 FB_COLS,
537 >
538{
539 #[inline]
540 fn erase(&mut self) {
541 self.0.erase();
542 }
543
544 #[inline]
545 fn set_pixel(&mut self, p: Point, color: Color) {
546 self.0.set_pixel(M::remap_point(p), color);
547 }
548}
549
550#[allow(deprecated)]
551impl<
552 F: FrameBuffer,
553 M: PixelRemapper,
554 const PANEL_ROWS: usize,
555 const PANEL_COLS: usize,
556 const NROWS: usize,
557 const BITS: u8,
558 const FRAME_COUNT: usize,
559 const TILE_ROWS: usize,
560 const TILE_COLS: usize,
561 const FB_COLS: usize,
562 > FrameBuffer
563 for TiledFrameBuffer<
564 F,
565 M,
566 PANEL_ROWS,
567 PANEL_COLS,
568 NROWS,
569 BITS,
570 FRAME_COUNT,
571 TILE_ROWS,
572 TILE_COLS,
573 FB_COLS,
574 >
575{
576 type Word = F::Word;
577
578 const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = F::BCM_SEQUENCE;
579
580 const BCM_SEQUENCE_LEN: usize = F::BCM_SEQUENCE_LEN;
581
582 const BCM_SEQUENCE_COUNT: usize = F::BCM_SEQUENCE_COUNT;
583
584 const BCM_SEGMENTS_PER_GROUP: usize = F::BCM_SEGMENTS_PER_GROUP;
585
586 fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
587 self.0.bcm_segment_ptr(index)
588 }
589}
590
591#[allow(deprecated)]
592impl<
593 F: MutableFrameBuffer,
594 M: PixelRemapper,
595 const PANEL_ROWS: usize,
596 const PANEL_COLS: usize,
597 const NROWS: usize,
598 const BITS: u8,
599 const FRAME_COUNT: usize,
600 const TILE_ROWS: usize,
601 const TILE_COLS: usize,
602 const FB_COLS: usize,
603 > MutableFrameBuffer
604 for TiledFrameBuffer<
605 F,
606 M,
607 PANEL_ROWS,
608 PANEL_COLS,
609 NROWS,
610 BITS,
611 FRAME_COUNT,
612 TILE_ROWS,
613 TILE_COLS,
614 FB_COLS,
615 >
616{
617}
618
619#[cfg(feature = "defmt")]
620#[allow(deprecated)]
621impl<
622 F: defmt::Format,
623 M: PixelRemapper,
624 const PANEL_ROWS: usize,
625 const PANEL_COLS: usize,
626 const NROWS: usize,
627 const BITS: u8,
628 const FRAME_COUNT: usize,
629 const TILE_ROWS: usize,
630 const TILE_COLS: usize,
631 const FB_COLS: usize,
632 > defmt::Format
633 for TiledFrameBuffer<
634 F,
635 M,
636 PANEL_ROWS,
637 PANEL_COLS,
638 NROWS,
639 BITS,
640 FRAME_COUNT,
641 TILE_ROWS,
642 TILE_COLS,
643 FB_COLS,
644 >
645{
646 fn format(&self, fmt: defmt::Formatter) {
647 defmt::write!(fmt, "TiledFrameBuffer({:?})", self.0);
648 }
649}
650
651#[cfg_attr(feature = "defmt", derive(defmt::Format))]
713#[derive(core::fmt::Debug)]
714pub struct RemappedFrameBuffer<F, M: PixelRemapper>(F, PhantomData<M>);
715
716impl<F: Default, M: PixelRemapper> RemappedFrameBuffer<F, M> {
717 #[must_use]
721 pub fn new() -> Self {
722 Self(F::default(), PhantomData)
723 }
724}
725
726impl<F: Default, M: PixelRemapper> Default for RemappedFrameBuffer<F, M> {
727 fn default() -> Self {
728 Self::new()
729 }
730}
731
732impl<F: DrawTarget<Error = Infallible, Color = Color>, M: PixelRemapper> DrawTarget
733 for RemappedFrameBuffer<F, M>
734{
735 type Color = Color;
736 type Error = Infallible;
737
738 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
739 where
740 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
741 {
742 self.0.draw_iter(pixels.into_iter().map(M::remap))
743 }
744}
745
746impl<F: DrawTarget<Error = Infallible, Color = Color>, M: PixelRemapper> OriginDimensions
747 for RemappedFrameBuffer<F, M>
748{
749 fn size(&self) -> Size {
750 Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
751 }
752}
753
754impl<F: FrameBufferOperations + FrameBuffer, M: PixelRemapper> FrameBufferOperations
755 for RemappedFrameBuffer<F, M>
756{
757 #[inline]
758 fn erase(&mut self) {
759 self.0.erase();
760 }
761
762 #[inline]
763 fn set_pixel(&mut self, p: Point, color: Color) {
764 self.0.set_pixel(M::remap_point(p), color);
765 }
766}
767
768impl<F: FrameBuffer, M: PixelRemapper> FrameBuffer for RemappedFrameBuffer<F, M> {
769 type Word = F::Word;
770
771 const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = F::BCM_SEQUENCE;
772
773 const BCM_SEQUENCE_LEN: usize = F::BCM_SEQUENCE_LEN;
774
775 const BCM_SEQUENCE_COUNT: usize = F::BCM_SEQUENCE_COUNT;
776
777 const BCM_SEGMENTS_PER_GROUP: usize = F::BCM_SEGMENTS_PER_GROUP;
778
779 fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
780 self.0.bcm_segment_ptr(index)
781 }
782}
783
784impl<F: MutableFrameBuffer, M: PixelRemapper> MutableFrameBuffer for RemappedFrameBuffer<F, M> {}
785
786#[cfg(test)]
787mod tests {
788 extern crate std;
789
790 use embedded_graphics::prelude::*;
791
792 use super::*;
793 use crate::MutableFrameBuffer;
794 use core::convert::Infallible;
795
796 #[test]
797 fn test_virtual_size_function_with_equal_rows_and_cols() {
798 const ROWS_IN_PANEL: usize = 32;
799 const COLS_IN_PANEL: usize = 64;
800 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
801 let virt_size = PanelChain::virtual_size();
802 assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL * 3));
803 }
804
805 #[test]
806 fn test_virtual_size_function_with_uneven_rows_and_cols() {
807 const ROWS_IN_PANEL: usize = 32;
808 const COLS_IN_PANEL: usize = 64;
809 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
810 let virt_size = PanelChain::virtual_size();
811 assert_eq!(virt_size, (ROWS_IN_PANEL * 5, COLS_IN_PANEL * 3));
812 }
813
814 #[test]
815 fn test_virtual_size_function_with_single_column() {
816 const ROWS_IN_PANEL: usize = 32;
817 const COLS_IN_PANEL: usize = 64;
818 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
819 let virt_size = PanelChain::virtual_size();
820 assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL));
821 }
822
823 #[test]
824 fn test_fb_size_function_with_equal_rows_and_cols() {
825 const ROWS_IN_PANEL: usize = 32;
826 const COLS_IN_PANEL: usize = 64;
827 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
828 let virt_size = PanelChain::fb_size();
829 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 9));
830 }
831
832 #[test]
833 fn test_fb_size_function_with_uneven_rows_and_cols() {
834 const ROWS_IN_PANEL: usize = 32;
835 const COLS_IN_PANEL: usize = 64;
836 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
837 let virt_size = PanelChain::fb_size();
838 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 15));
839 }
840
841 #[test]
842 fn test_fb_size_function_with_single_column() {
843 const ROWS_IN_PANEL: usize = 32;
844 const COLS_IN_PANEL: usize = 64;
845 type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
846 let virt_size = PanelChain::fb_size();
847 assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 3));
848 }
849
850 #[test]
851 fn test_pixel_remap_top_right_down_point_in_origin() {
852 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
853
854 let pixel = PanelChain::remap(Pixel(Point::new(0, 0), Color::RED));
855 assert_eq!(pixel.0, Point::new(384, 0));
856 }
857
858 #[test]
859 fn test_pixel_remap_top_right_down_point_in_bottom_left_corner() {
860 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
861
862 let pixel = PanelChain::remap(Pixel(Point::new(0, 95), Color::RED));
863 assert_eq!(pixel.0, Point::new(0, 31));
864 }
865
866 #[test]
867 fn test_pixel_remap_top_right_down_point_in_bottom_right_corner() {
868 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
869
870 let pixel = PanelChain::remap(Pixel(Point::new(191, 95), Color::RED));
871 assert_eq!(pixel.0, Point::new(191, 31));
872 }
873
874 #[test]
875 fn test_pixel_remap_top_right_down_point_on_x_right_edge_of_first_panel() {
876 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
877
878 let pixel = PanelChain::remap(Pixel(Point::new(63, 0), Color::RED));
879 assert_eq!(pixel.0, Point::new(447, 0));
880 }
881
882 #[test]
883 fn test_pixel_remap_top_right_down_point_on_x_left_edge_of_second_panel() {
884 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
885
886 let pixel = PanelChain::remap(Pixel(Point::new(64, 0), Color::RED));
887 assert_eq!(pixel.0, Point::new(448, 0));
888 }
889
890 #[test]
891 fn test_pixel_remap_top_right_down_point_on_y_bottom_edge_of_first_panel() {
892 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
893
894 let pixel = PanelChain::remap(Pixel(Point::new(0, 31), Color::RED));
895 assert_eq!(pixel.0, Point::new(384, 31));
896 }
897
898 #[test]
899 fn test_pixel_remap_top_right_down_point_on_y_top_edge_of_fourth_panel() {
900 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
901
902 let pixel = PanelChain::remap(Pixel(Point::new(0, 32), Color::RED));
903 assert_eq!(pixel.0, Point::new(383, 31));
904 }
905
906 #[test]
907 fn test_pixel_remap_top_right_down_point_slightly_to_the_top_middle() {
908 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
909
910 let pixel = PanelChain::remap(Pixel(Point::new(100, 40), Color::RED));
911 assert_eq!(pixel.0, Point::new(283, 23));
912 }
913
914 #[test]
915 fn test_pixel_remap_negative_pixel_does_not_remap() {
916 type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
917
918 let pixel = PanelChain::remap(Pixel(Point::new(-5, 40), Color::RED));
919 assert_eq!(pixel.0, Point::new(-5, 40));
920 }
921
922 #[test]
923 fn test_compute_tiled_cols() {
924 assert_eq!(192, compute_tiled_cols(32, 3, 2));
925 }
926
927 #[test]
928 #[allow(deprecated)]
929 fn test_tiling_framebuffer_canvas_size() {
930 use crate::plain::DmaFrameBuffer;
931 use crate::tiling::{compute_tiled_cols, ChainTopRightDown, TiledFrameBuffer};
932 use crate::{compute_frame_count, compute_rows};
933
934 const TILED_COLS: usize = 3;
935 const TILED_ROWS: usize = 3;
936 const ROWS: usize = 32;
937 const PANEL_COLS: usize = 64;
938 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
939 const BITS: u8 = 2;
940 const NROWS: usize = compute_rows(ROWS);
941 const FRAME_COUNT: usize = compute_frame_count(BITS);
942
943 type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
944 type TiledFBType = TiledFrameBuffer<
945 FBType,
946 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
947 ROWS,
948 PANEL_COLS,
949 NROWS,
950 BITS,
951 FRAME_COUNT,
952 TILED_ROWS,
953 TILED_COLS,
954 FB_COLS,
955 >;
956
957 let fb = TiledFBType::new();
958
959 assert_eq!(fb.size(), Size::new(192, 96));
960 }
961
962 struct TestFrameBuffer {
964 calls: std::cell::RefCell<std::vec::Vec<Call>>,
965 buf: [u8; 8],
966 }
967
968 #[derive(Debug, Clone, PartialEq, Eq)]
969 enum Call {
970 Erase,
971 SetPixel { p: Point, color: Color },
972 Draw(std::vec::Vec<(Point, Color)>),
973 }
974
975 impl TestFrameBuffer {
976 fn new() -> Self {
977 Self {
978 calls: std::cell::RefCell::new(std::vec::Vec::new()),
979 buf: [0; 8],
980 }
981 }
982
983 fn take_calls(&self) -> std::vec::Vec<Call> {
984 core::mem::take(&mut *self.calls.borrow_mut())
985 }
986 }
987
988 impl Default for TestFrameBuffer {
989 fn default() -> Self {
990 Self::new()
991 }
992 }
993
994 impl DrawTarget for TestFrameBuffer {
995 type Color = Color;
996 type Error = Infallible;
997
998 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
999 where
1000 I: IntoIterator<Item = Pixel<Self::Color>>,
1001 {
1002 let v = pixels.into_iter().map(|p| (p.0, p.1)).collect();
1003 self.calls.borrow_mut().push(Call::Draw(v));
1004 Ok(())
1005 }
1006 }
1007
1008 impl OriginDimensions for TestFrameBuffer {
1009 fn size(&self) -> Size {
1010 Size::new(1, 1)
1011 }
1012 }
1013
1014 impl FrameBuffer for TestFrameBuffer {
1015 type Word = u8;
1016
1017 const BCM_SEQUENCE: [crate::BcmLenReps; crate::BCM_SEQUENCE_CAPACITY] = {
1018 let mut seq = [crate::BcmLenReps::ZERO; crate::BCM_SEQUENCE_CAPACITY];
1019 seq[0] = crate::BcmLenReps { len: 8, reps: 1 };
1020 seq
1021 };
1022
1023 const BCM_SEQUENCE_LEN: usize = 1;
1024
1025 const BCM_SEQUENCE_COUNT: usize = 1;
1026
1027 fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
1028 assert!(index == 0);
1029 self.buf.as_ptr()
1030 }
1031 }
1032
1033 impl FrameBufferOperations for TestFrameBuffer {
1034 fn erase(&mut self) {
1035 self.calls.borrow_mut().push(Call::Erase);
1036 }
1037
1038 fn set_pixel(&mut self, p: Point, color: Color) {
1039 self.calls.borrow_mut().push(Call::SetPixel { p, color });
1040 }
1041 }
1042
1043 impl MutableFrameBuffer for TestFrameBuffer {}
1044
1045 #[test]
1046 #[allow(deprecated)]
1047 fn test_tiled_draw_iter_forwards_with_remap() {
1048 const TILED_COLS: usize = 3;
1049 const TILED_ROWS: usize = 3;
1050 const ROWS: usize = 32;
1051 const PANEL_COLS: usize = 64;
1052 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1053
1054 let mut fb = TiledFrameBuffer::<
1055 TestFrameBuffer,
1056 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1057 ROWS,
1058 PANEL_COLS,
1059 { crate::compute_rows(ROWS) },
1060 2,
1061 { crate::compute_frame_count(2) },
1062 TILED_ROWS,
1063 TILED_COLS,
1064 FB_COLS,
1065 >(TestFrameBuffer::new(), core::marker::PhantomData);
1066
1067 let input = [
1068 Pixel(Point::new(0, 0), Color::RED),
1069 Pixel(Point::new(63, 0), Color::GREEN),
1070 Pixel(Point::new(64, 0), Color::BLUE),
1071 Pixel(Point::new(100, 40), Color::WHITE),
1072 ];
1073
1074 fb.draw_iter(input.into_iter()).unwrap();
1075
1076 let calls = fb.0.take_calls();
1077 assert_eq!(calls.len(), 1);
1078 match &calls[0] {
1079 Call::Draw(v) => {
1080 let expected =
1081 [
1082 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
1083 Pixel(Point::new(0, 0), Color::RED),
1084 ),
1085 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
1086 Pixel(Point::new(63, 0), Color::GREEN),
1087 ),
1088 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
1089 Pixel(Point::new(64, 0), Color::BLUE),
1090 ),
1091 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
1092 Pixel(Point::new(100, 40), Color::WHITE),
1093 ),
1094 ];
1095 let expected_points: std::vec::Vec<(Point, Color)> =
1096 expected.iter().map(|p| (p.0, p.1)).collect();
1097 assert_eq!(v.as_slice(), expected_points.as_slice());
1098 }
1099 _ => panic!("expected a Draw call"),
1100 }
1101 }
1102
1103 #[test]
1104 #[allow(deprecated)]
1105 fn test_tiled_set_pixel_remaps_and_forwards() {
1106 const TILED_COLS: usize = 3;
1107 const TILED_ROWS: usize = 3;
1108 const ROWS: usize = 32;
1109 const PANEL_COLS: usize = 64;
1110 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1111
1112 let mut fb = TiledFrameBuffer::<
1113 TestFrameBuffer,
1114 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1115 ROWS,
1116 PANEL_COLS,
1117 { crate::compute_rows(ROWS) },
1118 2,
1119 { crate::compute_frame_count(2) },
1120 TILED_ROWS,
1121 TILED_COLS,
1122 FB_COLS,
1123 >(TestFrameBuffer::new(), core::marker::PhantomData);
1124
1125 let p = Point::new(100, 40);
1126 fb.set_pixel(p, Color::BLUE);
1127
1128 let calls = fb.0.take_calls();
1129 assert_eq!(calls.len(), 1);
1130 match calls.into_iter().next().unwrap() {
1131 Call::SetPixel { p: rp, color } => {
1132 let expected =
1133 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap_point(p);
1134 assert_eq!(rp, expected);
1135 assert_eq!(color, Color::BLUE);
1136 }
1137 _ => panic!("expected a SetPixel call"),
1138 }
1139 }
1140
1141 #[test]
1142 #[allow(deprecated)]
1143 fn test_tiled_erase_forwards() {
1144 const TILED_COLS: usize = 2;
1145 const TILED_ROWS: usize = 2;
1146 const ROWS: usize = 32;
1147 const PANEL_COLS: usize = 64;
1148 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1149
1150 let mut fb = TiledFrameBuffer::<
1151 TestFrameBuffer,
1152 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1153 ROWS,
1154 PANEL_COLS,
1155 { crate::compute_rows(ROWS) },
1156 2,
1157 { crate::compute_frame_count(2) },
1158 TILED_ROWS,
1159 TILED_COLS,
1160 FB_COLS,
1161 >(TestFrameBuffer::new(), core::marker::PhantomData);
1162 fb.erase();
1163 let calls = fb.0.take_calls();
1164 assert_eq!(calls, std::vec![Call::Erase]);
1165 }
1166
1167 #[test]
1168 #[allow(deprecated)]
1169 fn test_tiled_negative_coordinates_not_remapped() {
1170 const TILED_COLS: usize = 2;
1171 const TILED_ROWS: usize = 2;
1172 const ROWS: usize = 32;
1173 const PANEL_COLS: usize = 64;
1174 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1175
1176 let mut fb = TiledFrameBuffer::<
1177 TestFrameBuffer,
1178 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1179 ROWS,
1180 PANEL_COLS,
1181 { crate::compute_rows(ROWS) },
1182 2,
1183 { crate::compute_frame_count(2) },
1184 TILED_ROWS,
1185 TILED_COLS,
1186 FB_COLS,
1187 >(TestFrameBuffer::new(), core::marker::PhantomData);
1188
1189 let neg = Point::new(-3, 5);
1191 fb.set_pixel(neg, Color::GREEN);
1192 fb.draw_iter(core::iter::once(Pixel(Point::new(10, -2), Color::RED)))
1194 .unwrap();
1195
1196 let calls = fb.0.take_calls();
1197 assert_eq!(calls.len(), 2);
1198 assert!(matches!(calls[0], Call::SetPixel { p, .. } if p == neg));
1199 match &calls[1] {
1200 Call::Draw(v) => {
1201 assert_eq!(v.as_slice(), &[(Point::new(10, -2), Color::RED)]);
1202 }
1203 _ => panic!("expected a Draw call"),
1204 }
1205 }
1206
1207 struct Huge<
1209 const PANEL_ROWS: usize,
1210 const PANEL_COLS: usize,
1211 const TILE_ROWS: usize,
1212 const TILE_COLS: usize,
1213 >;
1214
1215 impl<
1216 const PANEL_ROWS: usize,
1217 const PANEL_COLS: usize,
1218 const TILE_ROWS: usize,
1219 const TILE_COLS: usize,
1220 > PixelRemapper for Huge<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
1221 {
1222 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
1223 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
1224 const FB_ROWS: usize = PANEL_ROWS;
1225 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
1226
1227 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
1228 (x + 70_000, y + 70_000)
1229 }
1230 }
1231
1232 #[test]
1233 #[allow(deprecated)]
1234 fn test_remap_point_truncates_to_u16_range() {
1235 const TILED_COLS: usize = 1;
1236 const TILED_ROWS: usize = 1;
1237 const ROWS: usize = 32;
1238 const PANEL_COLS: usize = 64;
1239 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1240
1241 let mut fb = TiledFrameBuffer::<
1242 TestFrameBuffer,
1243 Huge<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1244 ROWS,
1245 PANEL_COLS,
1246 { crate::compute_rows(ROWS) },
1247 2,
1248 { crate::compute_frame_count(2) },
1249 TILED_ROWS,
1250 TILED_COLS,
1251 FB_COLS,
1252 >(TestFrameBuffer::new(), core::marker::PhantomData);
1253 fb.set_pixel(Point::new(1, 2), Color::RED);
1254
1255 let calls = fb.0.take_calls();
1256 match calls.into_iter().next().unwrap() {
1257 Call::SetPixel { p, color } => {
1258 let (rx, ry) = (1usize + 70_000, 2usize + 70_000);
1259 let expected = Point::new(i32::from(rx as u16), i32::from(ry as u16));
1260 assert_eq!(p, expected);
1261 assert_eq!(color, Color::RED);
1262 }
1263 other => panic!("unexpected call recorded: {other:?}"),
1264 }
1265 }
1266
1267 #[test]
1268 fn test_more_compute_tiled_cols_cases() {
1269 assert_eq!(compute_tiled_cols(64, 1, 4), 256);
1270 assert_eq!(compute_tiled_cols(64, 4, 1), 256);
1271 assert_eq!(compute_tiled_cols(32, 4, 5), 640);
1272 }
1273
1274 #[test]
1275 #[allow(deprecated)]
1276 fn test_tiled_default_and_new_construct() {
1277 const TILED_COLS: usize = 4;
1278 const TILED_ROWS: usize = 2;
1279 const ROWS: usize = 32;
1280 const PANEL_COLS: usize = 64;
1281 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1282
1283 let fb_default = TiledFrameBuffer::<
1284 TestFrameBuffer,
1285 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1286 ROWS,
1287 PANEL_COLS,
1288 { crate::compute_rows(ROWS) },
1289 2,
1290 { crate::compute_frame_count(2) },
1291 TILED_ROWS,
1292 TILED_COLS,
1293 FB_COLS,
1294 >::default();
1295
1296 let fb_new = TiledFrameBuffer::<
1297 TestFrameBuffer,
1298 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1299 ROWS,
1300 PANEL_COLS,
1301 { crate::compute_rows(ROWS) },
1302 2,
1303 { crate::compute_frame_count(2) },
1304 TILED_ROWS,
1305 TILED_COLS,
1306 FB_COLS,
1307 >::new();
1308
1309 let expected_size = Size::new((PANEL_COLS * TILED_COLS) as u32, (ROWS * TILED_ROWS) as u32);
1311 assert_eq!(fb_default.size(), expected_size);
1312 assert_eq!(fb_new.size(), expected_size);
1313
1314 assert!(fb_default.0.take_calls().is_empty());
1316 assert!(fb_new.0.take_calls().is_empty());
1317 }
1318
1319 #[test]
1320 #[allow(deprecated)]
1321 fn test_tiled_origin_dimensions_matches_virtual_size() {
1322 const TILED_COLS: usize = 5;
1323 const TILED_ROWS: usize = 2;
1324 const ROWS: usize = 32;
1325 const PANEL_COLS: usize = 64;
1326 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1327
1328 let fb = TiledFrameBuffer::<
1329 TestFrameBuffer,
1330 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1331 ROWS,
1332 PANEL_COLS,
1333 { crate::compute_rows(ROWS) },
1334 2,
1335 { crate::compute_frame_count(2) },
1336 TILED_ROWS,
1337 TILED_COLS,
1338 FB_COLS,
1339 >::new();
1340
1341 let (virt_rows, virt_cols) = <ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS> as PixelRemapper>::virtual_size();
1342 assert_eq!(fb.size(), Size::new(virt_cols as u32, virt_rows as u32));
1343 }
1344
1345 fn expected_ctrdd_xy<const PR: usize, const PC: usize, const TR: usize, const TC: usize>(
1347 x: usize,
1348 y: usize,
1349 ) -> (usize, usize) {
1350 let row = y / PR;
1351 let base = (TR - 1 - row) * (PC * TC);
1352 if row % 2 == 1 {
1353 (base + (PC * TC) - 1 - x, PR - 1 - (y % PR))
1354 } else {
1355 (base + x, y % PR)
1356 }
1357 }
1358
1359 #[test]
1360 fn test_chain_top_right_down_corners_2x3() {
1361 const PR: usize = 32;
1362 const PC: usize = 64;
1363 const TR: usize = 2;
1364 const TC: usize = 3;
1365 type M = ChainTopRightDown<PR, PC, TR, TC>;
1366
1367 for r in 0..TR {
1368 for c in 0..TC {
1369 let x0 = c * PC;
1370 let y0 = r * PR;
1371 let corners = [
1372 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1377
1378 for &(x, y) in &corners {
1379 let got = <M as PixelRemapper>::remap_xy(x, y);
1380 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1381 assert_eq!(
1382 got, exp,
1383 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1384 r, c, x, y
1385 );
1386 }
1387 }
1388 }
1389 }
1390
1391 #[test]
1392 fn test_chain_top_right_down_corners_3x2() {
1393 const PR: usize = 32;
1394 const PC: usize = 64;
1395 const TR: usize = 3;
1396 const TC: usize = 2;
1397 type M = ChainTopRightDown<PR, PC, TR, TC>;
1398
1399 for r in 0..TR {
1400 for c in 0..TC {
1401 let x0 = c * PC;
1402 let y0 = r * PR;
1403 let corners = [
1404 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1409
1410 for &(x, y) in &corners {
1411 let got = <M as PixelRemapper>::remap_xy(x, y);
1412 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1413 assert_eq!(
1414 got, exp,
1415 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1416 r, c, x, y
1417 );
1418 }
1419 }
1420 }
1421 }
1422
1423 type TestRemapped = RemappedFrameBuffer<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>;
1426
1427 #[test]
1428 fn test_remapped_default_and_new_construct() {
1429 let _fb = TestRemapped::new();
1430 let _fb2: TestRemapped = Default::default();
1431 }
1432
1433 #[test]
1434 fn test_remapped_origin_dimensions_matches_virtual_size() {
1435 let fb = TestRemapped::new();
1436 assert_eq!(fb.size(), Size::new(192, 96));
1437 }
1438
1439 #[test]
1440 fn test_remapped_draw_iter_forwards_with_remap() {
1441 let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>(
1442 TestFrameBuffer::new(),
1443 core::marker::PhantomData,
1444 );
1445
1446 let input = [
1447 Pixel(Point::new(0, 0), Color::RED),
1448 Pixel(Point::new(63, 0), Color::GREEN),
1449 Pixel(Point::new(64, 0), Color::BLUE),
1450 Pixel(Point::new(100, 40), Color::WHITE),
1451 ];
1452
1453 fb.draw_iter(input.into_iter()).unwrap();
1454
1455 let calls = fb.0.take_calls();
1456 assert_eq!(calls.len(), 1);
1457 match &calls[0] {
1458 Call::Draw(v) => {
1459 let expected = [
1460 ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(Point::new(0, 0), Color::RED)),
1461 ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(
1462 Point::new(63, 0),
1463 Color::GREEN,
1464 )),
1465 ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(Point::new(64, 0), Color::BLUE)),
1466 ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(
1467 Point::new(100, 40),
1468 Color::WHITE,
1469 )),
1470 ];
1471 let expected_points: std::vec::Vec<(Point, Color)> =
1472 expected.iter().map(|p| (p.0, p.1)).collect();
1473 assert_eq!(v.as_slice(), expected_points.as_slice());
1474 }
1475 _ => panic!("expected a Draw call"),
1476 }
1477 }
1478
1479 #[test]
1480 fn test_remapped_set_pixel_remaps_and_forwards() {
1481 let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>(
1482 TestFrameBuffer::new(),
1483 core::marker::PhantomData,
1484 );
1485
1486 let p = Point::new(100, 40);
1487 fb.set_pixel(p, Color::BLUE);
1488
1489 let calls = fb.0.take_calls();
1490 assert_eq!(calls.len(), 1);
1491 match calls.into_iter().next().unwrap() {
1492 Call::SetPixel { p: rp, color } => {
1493 let expected = ChainTopRightDown::<32, 64, 3, 3>::remap_point(p);
1494 assert_eq!(rp, expected);
1495 assert_eq!(color, Color::BLUE);
1496 }
1497 _ => panic!("expected a SetPixel call"),
1498 }
1499 }
1500
1501 #[test]
1502 fn test_remapped_erase_forwards() {
1503 let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 2, 2>>(
1504 TestFrameBuffer::new(),
1505 core::marker::PhantomData,
1506 );
1507
1508 fb.erase();
1509 let calls = fb.0.take_calls();
1510 assert_eq!(calls, std::vec![Call::Erase]);
1511 }
1512
1513 #[test]
1514 fn test_remapped_negative_coordinates_not_remapped() {
1515 let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 2, 2>>(
1516 TestFrameBuffer::new(),
1517 core::marker::PhantomData,
1518 );
1519
1520 let neg = Point::new(-3, 5);
1521 fb.set_pixel(neg, Color::GREEN);
1522 fb.draw_iter(core::iter::once(Pixel(Point::new(10, -2), Color::RED)))
1523 .unwrap();
1524
1525 let calls = fb.0.take_calls();
1526 assert_eq!(calls.len(), 2);
1527 assert!(matches!(calls[0], Call::SetPixel { p, .. } if p == neg));
1528 match &calls[1] {
1529 Call::Draw(v) => {
1530 assert_eq!(v.as_slice(), &[(Point::new(10, -2), Color::RED)]);
1531 }
1532 _ => panic!("expected a Draw call"),
1533 }
1534 }
1535
1536 #[test]
1537 fn test_remapped_with_plain_framebuffer() {
1538 use crate::plain::DmaFrameBuffer;
1539 const TILED_COLS: usize = 3;
1540 const TILED_ROWS: usize = 3;
1541 const ROWS: usize = 32;
1542 const PANEL_COLS: usize = 64;
1543 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1544 const BITS: u8 = 2;
1545 const NROWS: usize = crate::compute_rows(ROWS);
1546 const FRAME_COUNT: usize = crate::compute_frame_count(BITS);
1547
1548 type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
1549 type Display = RemappedFrameBuffer<
1550 FBType,
1551 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1552 >;
1553
1554 let fb = Display::new();
1555 assert_eq!(fb.size(), Size::new(192, 96));
1556 }
1557
1558 #[test]
1559 fn test_remapped_with_latched_framebuffer() {
1560 use crate::latched::DmaFrameBuffer;
1561 const TILED_COLS: usize = 3;
1562 const TILED_ROWS: usize = 3;
1563 const ROWS: usize = 32;
1564 const PANEL_COLS: usize = 64;
1565 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1566 const BITS: u8 = 2;
1567 const NROWS: usize = crate::compute_rows(ROWS);
1568 const FRAME_COUNT: usize = crate::compute_frame_count(BITS);
1569
1570 type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
1571 type Display = RemappedFrameBuffer<
1572 FBType,
1573 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1574 >;
1575
1576 let fb = Display::new();
1577 assert_eq!(fb.size(), Size::new(192, 96));
1578 }
1579
1580 #[test]
1581 fn test_remapped_with_bitplane_plain_framebuffer() {
1582 use crate::bitplane::plain::DmaFrameBuffer;
1583 const TILED_COLS: usize = 3;
1584 const TILED_ROWS: usize = 3;
1585 const ROWS: usize = 32;
1586 const NROWS: usize = ROWS / 2;
1587 const PANEL_COLS: usize = 64;
1588 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1589 const PLANES: usize = 8;
1590
1591 type FBType = DmaFrameBuffer<NROWS, FB_COLS, PLANES>;
1592 type Display = RemappedFrameBuffer<
1593 FBType,
1594 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1595 >;
1596
1597 let fb = Display::new();
1598 assert_eq!(fb.size(), Size::new(192, 96));
1599 }
1600
1601 #[test]
1602 fn test_remapped_with_bitplane_latched_framebuffer() {
1603 use crate::bitplane::latched::DmaFrameBuffer;
1604 const TILED_COLS: usize = 3;
1605 const TILED_ROWS: usize = 3;
1606 const ROWS: usize = 32;
1607 const NROWS: usize = ROWS / 2;
1608 const PANEL_COLS: usize = 64;
1609 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1610 const PLANES: usize = 8;
1611
1612 type FBType = DmaFrameBuffer<NROWS, FB_COLS, PLANES>;
1613 type Display = RemappedFrameBuffer<
1614 FBType,
1615 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1616 >;
1617
1618 let fb = Display::new();
1619 assert_eq!(fb.size(), Size::new(192, 96));
1620 }
1621
1622 #[test]
1623 fn test_quarter_scan_associated_constants() {
1624 type QS = QuarterScan<64, 64>;
1625 assert_eq!(QS::VIRT_ROWS, 64);
1626 assert_eq!(QS::VIRT_COLS, 64);
1627 assert_eq!(QS::FB_ROWS, 32);
1628 assert_eq!(QS::FB_COLS, 128);
1629 assert_eq!(QS::virtual_size(), (64, 64));
1630 assert_eq!(QS::fb_size(), (32, 128));
1631 }
1632
1633 #[test]
1634 fn test_quarter_scan_remap_xy_groups() {
1635 type QS = QuarterScan<64, 64>;
1636 assert_eq!(QS::remap_xy(0, 0), (64, 0));
1638 assert_eq!(QS::remap_xy(63, 15), (127, 15));
1639 assert_eq!(QS::remap_xy(0, 16), (0, 0));
1641 assert_eq!(QS::remap_xy(63, 31), (63, 15));
1642 assert_eq!(QS::remap_xy(0, 32), (64, 16));
1644 assert_eq!(QS::remap_xy(63, 47), (127, 31));
1645 assert_eq!(QS::remap_xy(0, 48), (0, 16));
1647 assert_eq!(QS::remap_xy(63, 63), (63, 31));
1648 }
1649
1650 #[test]
1651 fn test_quarter_scan_remap_out_of_bounds_is_clipped() {
1652 type QS = QuarterScan<64, 64>;
1653 assert_eq!(QS::remap_xy(64, 0), (128, 32));
1656 assert_eq!(QS::remap_xy(0, 64), (128, 32));
1657 assert_eq!(QS::remap_xy(100, 100), (128, 32));
1658 assert_eq!(QS::remap_xy(63, 63), (63, 31));
1660 }
1661
1662 #[test]
1663 fn test_quarter_scan_offscreen_pixels_do_not_panic() {
1664 use crate::bitplane::plain::DmaFrameBuffer;
1668 type InnerFB = DmaFrameBuffer<16, 128, 4>;
1669 type Display = RemappedFrameBuffer<InnerFB, QuarterScan<64, 64>>;
1670
1671 let mut fb = Display::new();
1672 for x in 0..70 {
1673 for y in 57..70 {
1674 fb.set_pixel(Point::new(x, y), Color::WHITE);
1675 }
1676 }
1677 }
1678
1679 #[test]
1680 fn test_quarter_scan_remap_is_bijective_and_in_bounds() {
1681 use super::quarter_scan::*;
1682 fn check<V: Variant>() {
1683 let mut seen = [false; 32 * 128];
1684 for y in 0..QuarterScan::<64, 64, V>::VIRT_ROWS {
1685 for x in 0..QuarterScan::<64, 64, V>::VIRT_COLS {
1686 let (fb_x, fb_y) = QuarterScan::<64, 64, V>::remap_xy(x, y);
1687 assert!(
1688 fb_x < QuarterScan::<64, 64, V>::FB_COLS,
1689 "fb_x {fb_x} out of bounds"
1690 );
1691 assert!(
1692 fb_y < QuarterScan::<64, 64, V>::FB_ROWS,
1693 "fb_y {fb_y} out of bounds"
1694 );
1695 let idx = fb_y * QuarterScan::<64, 64, V>::FB_COLS + fb_x;
1696 assert!(!seen[idx], "duplicate mapping to ({fb_x}, {fb_y})");
1697 seen[idx] = true;
1698 }
1699 }
1700 assert!(seen.iter().all(|&s| s));
1702 }
1703 check::<Linear>();
1704 check::<SectionsSwapped>();
1705 check::<HalvesSwapped>();
1706 check::<Alternating>();
1707 }
1708
1709 #[test]
1710 fn test_quarter_scan_variant_tables_are_permutations() {
1711 use super::quarter_scan::*;
1712 fn assert_permutation<V: Variant>() {
1713 let mut seen = [false; 4];
1714 for &(channel, section) in V::SLOT.iter() {
1715 assert!(channel < 2 && section < 2, "slot out of range");
1716 let idx = channel * 2 + section;
1717 assert!(!seen[idx], "duplicate slot in variant table");
1718 seen[idx] = true;
1719 }
1720 assert!(
1721 seen.iter().all(|&s| s),
1722 "variant table must cover all four slots"
1723 );
1724 }
1725 assert_permutation::<Linear>();
1726 assert_permutation::<SectionsSwapped>();
1727 assert_permutation::<HalvesSwapped>();
1728 assert_permutation::<Alternating>();
1729 }
1730
1731 #[test]
1732 fn test_quarter_scan_variant_mappings() {
1733 use super::quarter_scan::*;
1734 assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 0), (0, 0));
1736 assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 16), (64, 0));
1737 assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 32), (0, 16));
1738 assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 48), (64, 16));
1739 assert_eq!(
1741 QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 0),
1742 (0, 16)
1743 );
1744 assert_eq!(
1745 QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 16),
1746 (64, 16)
1747 );
1748 assert_eq!(
1749 QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 32),
1750 (0, 0)
1751 );
1752 assert_eq!(
1753 QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 48),
1754 (64, 0)
1755 );
1756 assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 0), (0, 0));
1758 assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 16), (0, 16));
1759 assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 32), (64, 0));
1760 assert_eq!(
1761 QuarterScan::<64, 64, Alternating>::remap_xy(0, 48),
1762 (64, 16)
1763 );
1764 }
1765
1766 #[test]
1767 fn test_quarter_scan_remap_matches_inner_bitplane_geometry() {
1768 use crate::bitplane::plain::DmaFrameBuffer;
1772 type QS = QuarterScan<64, 64>;
1773 type InnerFB = DmaFrameBuffer<16, 128, 4>;
1774 type Display = RemappedFrameBuffer<InnerFB, QS>;
1775
1776 let mut fb = Display::new();
1777 assert_eq!(fb.size(), Size::new(64, 64));
1778 fb.set_pixel(Point::new(1, 2), Color::RED);
1780 fb.set_pixel(Point::new(1, 20), Color::GREEN);
1781 fb.set_pixel(Point::new(1, 40), Color::BLUE);
1782 fb.set_pixel(Point::new(1, 60), Color::WHITE);
1783 }
1784}