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