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