1use core::{convert::Infallible, marker::PhantomData};
10
11use crate::{Color, FrameBuffer, FrameBufferOperations, WordSize};
12#[cfg(not(feature = "esp-hal-dma"))]
13use embedded_dma::ReadBuffer;
14use embedded_graphics::prelude::{DrawTarget, OriginDimensions, PixelColor, Point, Size};
15#[cfg(feature = "esp-hal-dma")]
16use esp_hal::dma::ReadBuffer;
17
18#[must_use]
29pub const fn compute_tiled_cols(
30 cols: usize,
31 num_panels_wide: usize,
32 num_panels_high: usize,
33) -> usize {
34 cols * num_panels_wide * num_panels_high
35}
36
37pub trait PixelRemapper {
49 const VIRT_ROWS: usize;
51 const VIRT_COLS: usize;
53 const FB_ROWS: usize;
55 const FB_COLS: usize;
57
58 #[inline]
60 fn remap<C: PixelColor>(mut pixel: embedded_graphics::Pixel<C>) -> embedded_graphics::Pixel<C> {
61 pixel.0 = Self::remap_point(pixel.0);
62 pixel
63 }
64
65 #[inline]
67 #[must_use]
68 fn remap_point(mut point: Point) -> Point {
69 if point.x < 0 || point.y < 0 {
70 return point;
72 }
73 let (re_x, re_y) = Self::remap_xy(point.x as usize, point.y as usize);
74 point.x = i32::from(re_x as u16);
76 point.y = i32::from(re_y as u16);
77 point
78 }
79
80 fn remap_xy(x: usize, y: usize) -> (usize, usize);
82
83 #[inline]
85 #[must_use]
86 fn virtual_size() -> (usize, usize) {
87 (Self::VIRT_ROWS, Self::VIRT_COLS)
88 }
89
90 #[inline]
92 #[must_use]
93 fn fb_size() -> (usize, usize) {
94 (Self::FB_ROWS, Self::FB_COLS)
95 }
96}
97
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
115#[derive(core::fmt::Debug)]
116pub struct ChainTopRightDown<
117 const PANEL_ROWS: usize,
118 const PANEL_COLS: usize,
119 const TILE_ROWS: usize,
120 const TILE_COLS: usize,
121> {}
122
123impl<
124 const PANEL_ROWS: usize,
125 const PANEL_COLS: usize,
126 const TILE_ROWS: usize,
127 const TILE_COLS: usize,
128 > PixelRemapper for ChainTopRightDown<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
129{
130 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
131 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
132 const FB_ROWS: usize = PANEL_ROWS;
133 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
134
135 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
136 let row = y / PANEL_ROWS;
138 let base = (TILE_ROWS - 1 - row) * Self::VIRT_COLS;
139
140 if row % 2 == 1 {
141 (
143 base + Self::VIRT_COLS - 1 - x, PANEL_ROWS - 1 - (y % PANEL_ROWS), )
146 } else {
147 (base + x, y % PANEL_ROWS) }
149 }
150}
151
152#[cfg_attr(feature = "defmt", derive(defmt::Format))]
203#[derive(core::fmt::Debug)]
204pub struct TiledFrameBuffer<
205 F,
206 M: PixelRemapper,
207 const PANEL_ROWS: usize,
208 const PANEL_COLS: usize,
209 const NROWS: usize,
210 const BITS: u8,
211 const FRAME_COUNT: usize,
212 const TILE_ROWS: usize,
213 const TILE_COLS: usize,
214 const FB_COLS: usize,
215>(F, PhantomData<M>);
216
217impl<
218 F: Default,
219 M: PixelRemapper,
220 const PANEL_ROWS: usize,
221 const PANEL_COLS: usize,
222 const NROWS: usize,
223 const BITS: u8,
224 const FRAME_COUNT: usize,
225 const TILE_ROWS: usize,
226 const TILE_COLS: usize,
227 const FB_COLS: usize,
228 >
229 TiledFrameBuffer<
230 F,
231 M,
232 PANEL_ROWS,
233 PANEL_COLS,
234 NROWS,
235 BITS,
236 FRAME_COUNT,
237 TILE_ROWS,
238 TILE_COLS,
239 FB_COLS,
240 >
241{
242 #[must_use]
246 pub fn new() -> Self {
247 Self(F::default(), PhantomData)
248 }
249}
250
251impl<
252 F: Default,
253 M: PixelRemapper,
254 const PANEL_ROWS: usize,
255 const PANEL_COLS: usize,
256 const NROWS: usize,
257 const BITS: u8,
258 const FRAME_COUNT: usize,
259 const TILE_ROWS: usize,
260 const TILE_COLS: usize,
261 const FB_COLS: usize,
262 > Default
263 for TiledFrameBuffer<
264 F,
265 M,
266 PANEL_ROWS,
267 PANEL_COLS,
268 NROWS,
269 BITS,
270 FRAME_COUNT,
271 TILE_ROWS,
272 TILE_COLS,
273 FB_COLS,
274 >
275{
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281impl<
282 F: DrawTarget<Error = Infallible, Color = Color>,
283 M: PixelRemapper,
284 const PANEL_ROWS: usize,
285 const PANEL_COLS: usize,
286 const NROWS: usize,
287 const BITS: u8,
288 const FRAME_COUNT: usize,
289 const TILE_ROWS: usize,
290 const TILE_COLS: usize,
291 const FB_COLS: usize,
292 > DrawTarget
293 for TiledFrameBuffer<
294 F,
295 M,
296 PANEL_ROWS,
297 PANEL_COLS,
298 NROWS,
299 BITS,
300 FRAME_COUNT,
301 TILE_ROWS,
302 TILE_COLS,
303 FB_COLS,
304 >
305{
306 type Color = Color;
307 type Error = Infallible;
308
309 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
310 where
311 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
312 {
313 self.0.draw_iter(pixels.into_iter().map(M::remap))
314 }
315}
316
317impl<
318 F: DrawTarget<Error = Infallible, Color = Color>,
319 M: PixelRemapper,
320 const PANEL_ROWS: usize,
321 const PANEL_COLS: usize,
322 const NROWS: usize,
323 const BITS: u8,
324 const FRAME_COUNT: usize,
325 const TILE_ROWS: usize,
326 const TILE_COLS: usize,
327 const FB_COLS: usize,
328 > OriginDimensions
329 for TiledFrameBuffer<
330 F,
331 M,
332 PANEL_ROWS,
333 PANEL_COLS,
334 NROWS,
335 BITS,
336 FRAME_COUNT,
337 TILE_ROWS,
338 TILE_COLS,
339 FB_COLS,
340 >
341{
342 fn size(&self) -> Size {
343 Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
344 }
345}
346
347impl<
348 F: FrameBufferOperations<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
349 + FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>,
350 M: PixelRemapper,
351 const PANEL_ROWS: usize,
352 const PANEL_COLS: usize,
353 const NROWS: usize,
354 const BITS: u8,
355 const FRAME_COUNT: usize,
356 const TILE_ROWS: usize,
357 const TILE_COLS: usize,
358 const FB_COLS: usize,
359 > FrameBufferOperations<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
360 for TiledFrameBuffer<
361 F,
362 M,
363 PANEL_ROWS,
364 PANEL_COLS,
365 NROWS,
366 BITS,
367 FRAME_COUNT,
368 TILE_ROWS,
369 TILE_COLS,
370 FB_COLS,
371 >
372{
373 #[inline]
374 fn erase(&mut self) {
375 self.0.erase();
376 }
377
378 #[inline]
379 fn set_pixel(&mut self, p: Point, color: Color) {
380 self.0.set_pixel(M::remap_point(p), color);
381 }
382}
383
384#[cfg(not(feature = "esp-hal-dma"))]
385unsafe impl<
386 T,
387 F: ReadBuffer<Word = T>,
388 M: PixelRemapper,
389 const PANEL_ROWS: usize,
390 const PANEL_COLS: usize,
391 const NROWS: usize,
392 const BITS: u8,
393 const FRAME_COUNT: usize,
394 const TILE_ROWS: usize,
395 const TILE_COLS: usize,
396 const FB_COLS: usize,
397 > ReadBuffer
398 for TiledFrameBuffer<
399 F,
400 M,
401 PANEL_ROWS,
402 PANEL_COLS,
403 NROWS,
404 BITS,
405 FRAME_COUNT,
406 TILE_ROWS,
407 TILE_COLS,
408 FB_COLS,
409 >
410{
411 type Word = T;
412
413 unsafe fn read_buffer(&self) -> (*const T, usize) {
414 self.0.read_buffer()
415 }
416}
417
418#[cfg(feature = "esp-hal-dma")]
419unsafe impl<
420 F: ReadBuffer,
421 M: PixelRemapper,
422 const PANEL_ROWS: usize,
423 const PANEL_COLS: usize,
424 const NROWS: usize,
425 const BITS: u8,
426 const FRAME_COUNT: usize,
427 const TILE_ROWS: usize,
428 const TILE_COLS: usize,
429 const FB_COLS: usize,
430 > ReadBuffer
431 for TiledFrameBuffer<
432 F,
433 M,
434 PANEL_ROWS,
435 PANEL_COLS,
436 NROWS,
437 BITS,
438 FRAME_COUNT,
439 TILE_ROWS,
440 TILE_COLS,
441 FB_COLS,
442 >
443{
444 unsafe fn read_buffer(&self) -> (*const u8, usize) {
445 self.0.read_buffer()
446 }
447}
448
449impl<
450 F: FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>,
451 M: PixelRemapper,
452 const PANEL_ROWS: usize,
453 const PANEL_COLS: usize,
454 const NROWS: usize,
455 const BITS: u8,
456 const FRAME_COUNT: usize,
457 const TILE_ROWS: usize,
458 const TILE_COLS: usize,
459 const FB_COLS: usize,
460 > FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
461 for TiledFrameBuffer<
462 F,
463 M,
464 PANEL_ROWS,
465 PANEL_COLS,
466 NROWS,
467 BITS,
468 FRAME_COUNT,
469 TILE_ROWS,
470 TILE_COLS,
471 FB_COLS,
472 >
473{
474 fn get_word_size(&self) -> WordSize {
475 self.0.get_word_size()
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 extern crate std;
482
483 use embedded_graphics::prelude::*;
484
485 use super::*;
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<
708 const ROWS: usize,
709 const COLS: usize,
710 const NROWS: usize,
711 const BITS: u8,
712 const FRAME_COUNT: usize,
713 > FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT> for TestFrameBuffer
714 {
715 fn get_word_size(&self) -> WordSize {
716 self.word_size
717 }
718 }
719
720 impl<
721 const ROWS: usize,
722 const COLS: usize,
723 const NROWS: usize,
724 const BITS: u8,
725 const FRAME_COUNT: usize,
726 > FrameBufferOperations<ROWS, COLS, NROWS, BITS, FRAME_COUNT> for TestFrameBuffer
727 {
728 fn erase(&mut self) {
729 self.calls.borrow_mut().push(Call::Erase);
730 }
731
732 fn set_pixel(&mut self, p: Point, color: Color) {
733 self.calls.borrow_mut().push(Call::SetPixel { p, color });
734 }
735 }
736
737 #[cfg(not(feature = "esp-hal-dma"))]
738 unsafe impl embedded_dma::ReadBuffer for TestFrameBuffer {
739 type Word = u8;
740
741 unsafe fn read_buffer(&self) -> (*const u8, usize) {
742 (self.buf.as_ptr(), self.buf.len())
743 }
744 }
745
746 #[cfg(feature = "esp-hal-dma")]
747 unsafe impl esp_hal::dma::ReadBuffer for TestFrameBuffer {
748 unsafe fn read_buffer(&self) -> (*const u8, usize) {
749 (self.buf.as_ptr(), self.buf.len())
750 }
751 }
752
753 #[test]
754 fn test_tiled_draw_iter_forwards_with_remap() {
755 const TILED_COLS: usize = 3;
756 const TILED_ROWS: usize = 3;
757 const ROWS: usize = 32;
758 const PANEL_COLS: usize = 64;
759 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
760
761 let mut fb = TiledFrameBuffer::<
762 TestFrameBuffer,
763 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
764 ROWS,
765 PANEL_COLS,
766 { crate::compute_rows(ROWS) },
767 2,
768 { crate::compute_frame_count(2) },
769 TILED_ROWS,
770 TILED_COLS,
771 FB_COLS,
772 >(
773 TestFrameBuffer::new(WordSize::Eight),
774 core::marker::PhantomData,
775 );
776
777 let input = [
778 Pixel(Point::new(0, 0), Color::RED),
779 Pixel(Point::new(63, 0), Color::GREEN),
780 Pixel(Point::new(64, 0), Color::BLUE),
781 Pixel(Point::new(100, 40), Color::WHITE),
782 ];
783
784 fb.draw_iter(input.into_iter()).unwrap();
785
786 let calls = fb.0.take_calls();
787 assert_eq!(calls.len(), 1);
788 match &calls[0] {
789 Call::Draw(v) => {
790 let expected =
791 [
792 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
793 Pixel(Point::new(0, 0), Color::RED),
794 ),
795 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
796 Pixel(Point::new(63, 0), Color::GREEN),
797 ),
798 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
799 Pixel(Point::new(64, 0), Color::BLUE),
800 ),
801 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
802 Pixel(Point::new(100, 40), Color::WHITE),
803 ),
804 ];
805 let expected_points: std::vec::Vec<(Point, Color)> =
806 expected.iter().map(|p| (p.0, p.1)).collect();
807 assert_eq!(v.as_slice(), expected_points.as_slice());
808 }
809 _ => panic!("expected a Draw call"),
810 }
811 }
812
813 #[test]
814 fn test_tiled_set_pixel_remaps_and_forwards() {
815 const TILED_COLS: usize = 3;
816 const TILED_ROWS: usize = 3;
817 const ROWS: usize = 32;
818 const PANEL_COLS: usize = 64;
819 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
820
821 let mut fb = TiledFrameBuffer::<
822 TestFrameBuffer,
823 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
824 ROWS,
825 PANEL_COLS,
826 { crate::compute_rows(ROWS) },
827 2,
828 { crate::compute_frame_count(2) },
829 TILED_ROWS,
830 TILED_COLS,
831 FB_COLS,
832 >(
833 TestFrameBuffer::new(WordSize::Eight),
834 core::marker::PhantomData,
835 );
836
837 let p = Point::new(100, 40);
838 fb.set_pixel(p, Color::BLUE);
839
840 let calls = fb.0.take_calls();
841 assert_eq!(calls.len(), 1);
842 match calls.into_iter().next().unwrap() {
843 Call::SetPixel { p: rp, color } => {
844 let expected =
845 ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap_point(p);
846 assert_eq!(rp, expected);
847 assert_eq!(color, Color::BLUE);
848 }
849 _ => panic!("expected a SetPixel call"),
850 }
851 }
852
853 #[test]
854 fn test_tiled_erase_forwards() {
855 const TILED_COLS: usize = 2;
856 const TILED_ROWS: usize = 2;
857 const ROWS: usize = 32;
858 const PANEL_COLS: usize = 64;
859 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
860
861 let mut fb = TiledFrameBuffer::<
862 TestFrameBuffer,
863 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
864 ROWS,
865 PANEL_COLS,
866 { crate::compute_rows(ROWS) },
867 2,
868 { crate::compute_frame_count(2) },
869 TILED_ROWS,
870 TILED_COLS,
871 FB_COLS,
872 >(
873 TestFrameBuffer::new(WordSize::Eight),
874 core::marker::PhantomData,
875 );
876 fb.erase();
877 let calls = fb.0.take_calls();
878 assert_eq!(calls, std::vec![Call::Erase]);
879 }
880
881 #[test]
882 fn test_tiled_negative_coordinates_not_remapped() {
883 const TILED_COLS: usize = 2;
884 const TILED_ROWS: usize = 2;
885 const ROWS: usize = 32;
886 const PANEL_COLS: usize = 64;
887 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
888
889 let mut fb = TiledFrameBuffer::<
890 TestFrameBuffer,
891 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
892 ROWS,
893 PANEL_COLS,
894 { crate::compute_rows(ROWS) },
895 2,
896 { crate::compute_frame_count(2) },
897 TILED_ROWS,
898 TILED_COLS,
899 FB_COLS,
900 >(
901 TestFrameBuffer::new(WordSize::Eight),
902 core::marker::PhantomData,
903 );
904
905 let neg = Point::new(-3, 5);
907 fb.set_pixel(neg, Color::GREEN);
908 fb.draw_iter(core::iter::once(Pixel(Point::new(10, -2), Color::RED)))
910 .unwrap();
911
912 let calls = fb.0.take_calls();
913 assert_eq!(calls.len(), 2);
914 assert!(matches!(calls[0], Call::SetPixel { p, .. } if p == neg));
915 match &calls[1] {
916 Call::Draw(v) => {
917 assert_eq!(v.as_slice(), &[(Point::new(10, -2), Color::RED)]);
918 }
919 _ => panic!("expected a Draw call"),
920 }
921 }
922
923 #[test]
924 fn test_tiled_read_buffer_passthrough() {
925 const TILED_COLS: usize = 2;
926 const TILED_ROWS: usize = 2;
927 const ROWS: usize = 32;
928 const PANEL_COLS: usize = 64;
929 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
930
931 let fb = TiledFrameBuffer::<
932 TestFrameBuffer,
933 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
934 ROWS,
935 PANEL_COLS,
936 { crate::compute_rows(ROWS) },
937 2,
938 { crate::compute_frame_count(2) },
939 TILED_ROWS,
940 TILED_COLS,
941 FB_COLS,
942 >(
943 TestFrameBuffer::new(WordSize::Eight),
944 core::marker::PhantomData,
945 );
946
947 let inner_ptr = fb.0.buf.as_ptr();
948 let inner_len = fb.0.buf.len();
949
950 let (ptr, len) = unsafe { fb.read_buffer() };
951 assert_eq!(ptr, inner_ptr);
952 assert_eq!(len, inner_len);
953 }
954
955 #[test]
956 fn test_tiled_get_word_size_passthrough() {
957 const TILED_COLS: usize = 2;
958 const TILED_ROWS: usize = 2;
959 const ROWS: usize = 32;
960 const PANEL_COLS: usize = 64;
961 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
962
963 let fb = TiledFrameBuffer::<
964 TestFrameBuffer,
965 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
966 ROWS,
967 PANEL_COLS,
968 { crate::compute_rows(ROWS) },
969 2,
970 { crate::compute_frame_count(2) },
971 TILED_ROWS,
972 TILED_COLS,
973 FB_COLS,
974 >(
975 TestFrameBuffer::new(WordSize::Sixteen),
976 core::marker::PhantomData,
977 );
978 assert_eq!(fb.get_word_size(), WordSize::Sixteen);
979 }
980
981 #[test]
982 fn test_tiled_get_word_size_eight_passthrough() {
983 const TILED_COLS: usize = 2;
984 const TILED_ROWS: usize = 2;
985 const ROWS: usize = 32;
986 const PANEL_COLS: usize = 64;
987 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
988
989 let fb = TiledFrameBuffer::<
990 TestFrameBuffer,
991 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
992 ROWS,
993 PANEL_COLS,
994 { crate::compute_rows(ROWS) },
995 2,
996 { crate::compute_frame_count(2) },
997 TILED_ROWS,
998 TILED_COLS,
999 FB_COLS,
1000 >(
1001 TestFrameBuffer::new(WordSize::Eight),
1002 core::marker::PhantomData,
1003 );
1004 assert_eq!(fb.get_word_size(), WordSize::Eight);
1005 }
1006
1007 struct Huge<
1009 const PANEL_ROWS: usize,
1010 const PANEL_COLS: usize,
1011 const TILE_ROWS: usize,
1012 const TILE_COLS: usize,
1013 >;
1014
1015 impl<
1016 const PANEL_ROWS: usize,
1017 const PANEL_COLS: usize,
1018 const TILE_ROWS: usize,
1019 const TILE_COLS: usize,
1020 > PixelRemapper for Huge<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
1021 {
1022 const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
1023 const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
1024 const FB_ROWS: usize = PANEL_ROWS;
1025 const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
1026
1027 fn remap_xy(x: usize, y: usize) -> (usize, usize) {
1028 (x + 70_000, y + 70_000)
1029 }
1030 }
1031
1032 #[test]
1033 fn test_remap_point_truncates_to_u16_range() {
1034 const TILED_COLS: usize = 1;
1035 const TILED_ROWS: usize = 1;
1036 const ROWS: usize = 32;
1037 const PANEL_COLS: usize = 64;
1038 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1039
1040 let mut fb = TiledFrameBuffer::<
1041 TestFrameBuffer,
1042 Huge<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1043 ROWS,
1044 PANEL_COLS,
1045 { crate::compute_rows(ROWS) },
1046 2,
1047 { crate::compute_frame_count(2) },
1048 TILED_ROWS,
1049 TILED_COLS,
1050 FB_COLS,
1051 >(
1052 TestFrameBuffer::new(WordSize::Eight),
1053 core::marker::PhantomData,
1054 );
1055 fb.set_pixel(Point::new(1, 2), Color::RED);
1056
1057 let calls = fb.0.take_calls();
1058 match calls.into_iter().next().unwrap() {
1059 Call::SetPixel { p, color } => {
1060 let (rx, ry) = (1usize + 70_000, 2usize + 70_000);
1061 let expected = Point::new(i32::from(rx as u16), i32::from(ry as u16));
1062 assert_eq!(p, expected);
1063 assert_eq!(color, Color::RED);
1064 }
1065 other => panic!("unexpected call recorded: {other:?}"),
1066 }
1067 }
1068
1069 #[test]
1070 fn test_more_compute_tiled_cols_cases() {
1071 assert_eq!(compute_tiled_cols(64, 1, 4), 256);
1072 assert_eq!(compute_tiled_cols(64, 4, 1), 256);
1073 assert_eq!(compute_tiled_cols(32, 4, 5), 640);
1074 }
1075
1076 #[test]
1077 fn test_tiled_default_and_new_construct() {
1078 const TILED_COLS: usize = 4;
1079 const TILED_ROWS: usize = 2;
1080 const ROWS: usize = 32;
1081 const PANEL_COLS: usize = 64;
1082 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1083
1084 let fb_default = TiledFrameBuffer::<
1085 TestFrameBuffer,
1086 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1087 ROWS,
1088 PANEL_COLS,
1089 { crate::compute_rows(ROWS) },
1090 2,
1091 { crate::compute_frame_count(2) },
1092 TILED_ROWS,
1093 TILED_COLS,
1094 FB_COLS,
1095 >::default();
1096
1097 let fb_new = TiledFrameBuffer::<
1098 TestFrameBuffer,
1099 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1100 ROWS,
1101 PANEL_COLS,
1102 { crate::compute_rows(ROWS) },
1103 2,
1104 { crate::compute_frame_count(2) },
1105 TILED_ROWS,
1106 TILED_COLS,
1107 FB_COLS,
1108 >::new();
1109
1110 assert_eq!(fb_default.get_word_size(), WordSize::Eight);
1112 assert_eq!(fb_new.get_word_size(), WordSize::Eight);
1113
1114 let expected_size = Size::new((PANEL_COLS * TILED_COLS) as u32, (ROWS * TILED_ROWS) as u32);
1116 assert_eq!(fb_default.size(), expected_size);
1117 assert_eq!(fb_new.size(), expected_size);
1118
1119 assert!(fb_default.0.take_calls().is_empty());
1121 assert!(fb_new.0.take_calls().is_empty());
1122 }
1123
1124 #[test]
1125 fn test_tiled_origin_dimensions_matches_virtual_size() {
1126 const TILED_COLS: usize = 5;
1127 const TILED_ROWS: usize = 2;
1128 const ROWS: usize = 32;
1129 const PANEL_COLS: usize = 64;
1130 const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
1131
1132 let fb = TiledFrameBuffer::<
1133 TestFrameBuffer,
1134 ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
1135 ROWS,
1136 PANEL_COLS,
1137 { crate::compute_rows(ROWS) },
1138 2,
1139 { crate::compute_frame_count(2) },
1140 TILED_ROWS,
1141 TILED_COLS,
1142 FB_COLS,
1143 >::new();
1144
1145 let (virt_rows, virt_cols) = <ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS> as PixelRemapper>::virtual_size();
1146 assert_eq!(fb.size(), Size::new(virt_cols as u32, virt_rows as u32));
1147 }
1148
1149 fn expected_ctrdd_xy<const PR: usize, const PC: usize, const TR: usize, const TC: usize>(
1151 x: usize,
1152 y: usize,
1153 ) -> (usize, usize) {
1154 let row = y / PR;
1155 let base = (TR - 1 - row) * (PC * TC);
1156 if row % 2 == 1 {
1157 (base + (PC * TC) - 1 - x, PR - 1 - (y % PR))
1158 } else {
1159 (base + x, y % PR)
1160 }
1161 }
1162
1163 #[test]
1164 fn test_chain_top_right_down_corners_2x3() {
1165 const PR: usize = 32;
1166 const PC: usize = 64;
1167 const TR: usize = 2;
1168 const TC: usize = 3;
1169 type M = ChainTopRightDown<PR, PC, TR, TC>;
1170
1171 for r in 0..TR {
1172 for c in 0..TC {
1173 let x0 = c * PC;
1174 let y0 = r * PR;
1175 let corners = [
1176 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1181
1182 for &(x, y) in &corners {
1183 let got = <M as PixelRemapper>::remap_xy(x, y);
1184 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1185 assert_eq!(
1186 got, exp,
1187 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1188 r, c, x, y
1189 );
1190 }
1191 }
1192 }
1193 }
1194
1195 #[test]
1196 fn test_chain_top_right_down_corners_3x2() {
1197 const PR: usize = 32;
1198 const PC: usize = 64;
1199 const TR: usize = 3;
1200 const TC: usize = 2;
1201 type M = ChainTopRightDown<PR, PC, TR, TC>;
1202
1203 for r in 0..TR {
1204 for c in 0..TC {
1205 let x0 = c * PC;
1206 let y0 = r * PR;
1207 let corners = [
1208 (x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
1213
1214 for &(x, y) in &corners {
1215 let got = <M as PixelRemapper>::remap_xy(x, y);
1216 let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
1217 assert_eq!(
1218 got, exp,
1219 "corner mismatch at panel (row={}, col={}), virtual=({}, {})",
1220 r, c, x, y
1221 );
1222 }
1223 }
1224 }
1225 }
1226}