1use core::convert::Infallible;
71
72use bitfield::bitfield;
73use embedded_graphics::pixelcolor::RgbColor;
74use embedded_graphics::prelude::{DrawTarget, OriginDimensions, Point, Size};
75
76use crate::Color;
77use crate::FrameBuffer;
78use crate::{FrameBufferOperations, MutableFrameBuffer};
79
80#[cfg(feature = "lead-blank-1")]
81const LEAD_BLANK_DELAY: usize = 1;
82#[cfg(feature = "lead-blank-2")]
83const LEAD_BLANK_DELAY: usize = 2;
84#[cfg(feature = "lead-blank-4")]
85const LEAD_BLANK_DELAY: usize = 4;
86#[cfg(feature = "lead-blank-8")]
87const LEAD_BLANK_DELAY: usize = 8;
88#[cfg(feature = "lead-blank-16")]
89const LEAD_BLANK_DELAY: usize = 16;
90
91#[cfg(not(any(
93 feature = "lead-blank-1",
94 feature = "lead-blank-2",
95 feature = "lead-blank-4",
96 feature = "lead-blank-8",
97 feature = "lead-blank-16"
98)))]
99const LEAD_BLANK_DELAY: usize = 1;
100
101#[cfg(feature = "trail-blank-1")]
102const TRAIL_BLANK_DELAY: usize = 1;
103#[cfg(feature = "trail-blank-2")]
104const TRAIL_BLANK_DELAY: usize = 2;
105#[cfg(feature = "trail-blank-4")]
106const TRAIL_BLANK_DELAY: usize = 4;
107#[cfg(feature = "trail-blank-8")]
108const TRAIL_BLANK_DELAY: usize = 8;
109#[cfg(feature = "trail-blank-16")]
110const TRAIL_BLANK_DELAY: usize = 16;
111
112#[cfg(not(any(
114 feature = "trail-blank-1",
115 feature = "trail-blank-2",
116 feature = "trail-blank-4",
117 feature = "trail-blank-8",
118 feature = "trail-blank-16"
119)))]
120const TRAIL_BLANK_DELAY: usize = 1;
121
122#[cfg(feature = "inter-row-blank-4")]
123const INTER_ROW_BLANK: usize = 4;
124#[cfg(feature = "inter-row-blank-8")]
125const INTER_ROW_BLANK: usize = 8;
126#[cfg(feature = "inter-row-blank-16")]
127const INTER_ROW_BLANK: usize = 16;
128#[cfg(feature = "inter-row-blank-32")]
129const INTER_ROW_BLANK: usize = 32;
130#[cfg(not(any(
131 feature = "inter-row-blank-4",
132 feature = "inter-row-blank-8",
133 feature = "inter-row-blank-16",
134 feature = "inter-row-blank-32"
135)))]
136const INTER_ROW_BLANK: usize = 0;
137
138#[cfg(not(feature = "invert-oe"))]
139const OE_ACTIVE: u16 = 0b1_0000_0000;
140#[cfg(not(feature = "invert-oe"))]
141const OE_BLANK: u16 = 0;
142
143#[cfg(feature = "invert-oe")]
144const OE_ACTIVE: u16 = 0;
145#[cfg(feature = "invert-oe")]
146const OE_BLANK: u16 = 0b1_0000_0000;
147
148#[inline]
149const fn map_index(i: usize) -> usize {
150 #[cfg(feature = "esp32-ordering")]
151 {
152 i ^ 1
153 }
154 #[cfg(not(feature = "esp32-ordering"))]
155 {
156 i
157 }
158}
159
160#[inline]
161const fn make_data_template<const COLS: usize>(addr: u8) -> [Entry; COLS] {
162 let mut data = [Entry::new(); COLS];
163 let mut i = 0;
164
165 while i < COLS {
166 let mut entry = Entry::new();
167 entry.0 = addr as u16 | OE_BLANK;
168
169 if i == COLS - 1 {
170 entry.0 |= 0b0010_0000; } else if i >= TRAIL_BLANK_DELAY && i < COLS.saturating_sub(LEAD_BLANK_DELAY + 1) {
172 entry.0 = (entry.0 & !0b1_0000_0000) | OE_ACTIVE;
173 }
174
175 data[map_index(i)] = entry;
176 i += 1;
177 }
178
179 data
180}
181
182bitfield! {
183 #[derive(Clone, Copy, Default, PartialEq)]
184 #[repr(transparent)]
185 pub(crate) struct Entry(u16);
186 pub(crate) dummy2, set_dummy2: 15;
187 pub(crate) blu2, set_blu2: 14;
188 pub(crate) grn2, set_grn2: 13;
189 pub(crate) red2, set_red2: 12;
190 pub(crate) blu1, set_blu1: 11;
191 pub(crate) grn1, set_grn1: 10;
192 pub(crate) red1, set_red1: 9;
193 pub(crate) output_enable, set_output_enable: 8;
194 pub(crate) dummy1, set_dummy1: 7;
195 pub(crate) dummy0, set_dummy0: 6;
196 pub(crate) latch, set_latch: 5;
197 pub(crate) addr, set_addr: 4, 0;
198}
199
200impl core::fmt::Debug for Entry {
201 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
202 f.debug_tuple("Entry")
203 .field(&format_args!("{:#x}", self.0))
204 .finish()
205 }
206}
207
208impl Entry {
209 const fn new() -> Self {
210 Self(OE_BLANK)
211 }
212
213 const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; const COLOR1_MASK: u16 = 0b0111_0000_0000_0000; #[inline]
217 fn set_color0_bits(&mut self, bits: u8) {
218 let bits16 = u16::from(bits) << 9;
219 self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
220 }
221
222 #[inline]
223 fn set_color1_bits(&mut self, bits: u8) {
224 let bits16 = u16::from(bits) << 12;
225 self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
226 }
227}
228
229#[derive(Clone, Copy, PartialEq, Debug)]
230#[repr(C)]
231pub struct Row<const COLS: usize> {
236 pub(crate) data: [Entry; COLS],
237 pub(crate) gap: [Entry; INTER_ROW_BLANK],
238}
239
240impl<const COLS: usize> Row<COLS> {
241 #[must_use]
245 pub const fn new() -> Self {
246 Self {
247 data: [Entry::new(); COLS],
248 gap: [Entry::new(); INTER_ROW_BLANK],
249 }
250 }
251
252 #[inline]
257 pub const fn format(&mut self, prev_addr: u8) {
258 let template = make_data_template::<COLS>(prev_addr);
259 let mut i = 0;
260 while i < COLS {
261 self.data[i] = template[i];
262 i += 1;
263 }
264
265 let gap_val = (prev_addr as u16) | OE_BLANK;
266 let mut i = 0;
267 #[allow(clippy::absurd_extreme_comparisons)]
269 while i < INTER_ROW_BLANK {
270 self.gap[i].0 = gap_val;
271 i += 1;
272 }
273 }
274}
275
276impl<const COLS: usize> Default for Row<COLS> {
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282#[derive(Clone, Copy, PartialEq, Debug)]
285#[repr(C)]
286pub struct PlaneData<const NROWS: usize, const COLS: usize> {
287 pub(crate) rows: [Row<COLS>; NROWS],
288 #[cfg(all(feature = "tail-closes-latch", feature = "esp32-ordering"))]
293 pub(crate) padding: Entry,
294 #[cfg(feature = "tail-closes-latch")]
295 pub(crate) tail: Entry,
296}
297
298impl<const NROWS: usize, const COLS: usize> PlaneData<NROWS, COLS> {
299 const fn new() -> Self {
300 Self {
301 rows: [Row::new(); NROWS],
302 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
303 padding: Entry::new(),
304 #[cfg(feature = "tail-closes-latch")]
305 tail: Entry::new(),
306 }
307 }
308}
309
310#[derive(Copy, Clone)]
312#[repr(C)]
313pub struct DmaFrameBuffer<const NROWS: usize, const COLS: usize, const PLANES: usize> {
314 pub(crate) planes: [PlaneData<NROWS, COLS>; PLANES],
315}
316
317impl<const NROWS: usize, const COLS: usize, const PLANES: usize>
318 DmaFrameBuffer<NROWS, COLS, PLANES>
319{
320 #[must_use]
322 pub const fn new() -> Self {
323 let mut instance = Self {
324 planes: [PlaneData::new(); PLANES],
325 };
326 instance.format();
327 instance
328 }
329
330 #[must_use]
332 pub const fn bcm_chunk_count() -> usize {
333 PLANES
334 }
335
336 #[must_use]
338 pub const fn bcm_chunk_bytes() -> usize {
339 core::mem::size_of::<PlaneData<NROWS, COLS>>()
340 }
341
342 #[inline]
344 pub const fn format(&mut self) {
345 let mut p = 0;
346 while p < PLANES {
347 let mut row_idx = 0;
348 while row_idx < NROWS {
349 let prev_addr = if row_idx == 0 {
350 NROWS as u8 - 1
351 } else {
352 row_idx as u8 - 1
353 };
354 self.planes[p].rows[row_idx].format(prev_addr);
355 row_idx += 1;
356 }
357 #[cfg(feature = "tail-closes-latch")]
358 {
359 self.planes[p].tail.0 = 0x1f | OE_BLANK;
360 }
361 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
362 {
363 self.planes[p].padding.0 = 0x1f | OE_BLANK;
364 }
365 p += 1;
366 }
367 }
368
369 #[inline]
371 pub fn erase(&mut self) {
372 const MASK: u16 = !0b0111_1110_0000_0000; for plane in &mut self.planes {
374 for row in &mut plane.rows {
375 for entry in &mut row.data {
376 entry.0 &= MASK;
377 }
378 }
379 }
380 }
381
382 #[inline]
384 pub fn set_pixel(&mut self, p: Point, color: Color) {
385 if p.x < 0 || p.y < 0 {
386 return;
387 }
388 self.set_pixel_internal(p.x as usize, p.y as usize, color);
389 }
390
391 #[inline]
392 fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
393 if x >= COLS || y >= NROWS * 2 {
394 return;
395 }
396
397 let row_idx = if y < NROWS { y } else { y - NROWS };
398 let is_top = y < NROWS;
399 let red = color.r();
400 let green = color.g();
401 let blue = color.b();
402
403 for plane_idx in 0..PLANES {
404 let bit = 7_u32.saturating_sub(plane_idx as u32);
405 let bits = ((u8::from(((blue >> bit) & 1) != 0)) << 2)
406 | ((u8::from(((green >> bit) & 1) != 0)) << 1)
407 | u8::from(((red >> bit) & 1) != 0);
408 let col_idx = map_index(x);
409 let entry = &mut self.planes[plane_idx].rows[row_idx].data[col_idx];
410 if is_top {
411 entry.set_color0_bits(bits);
412 } else {
413 entry.set_color1_bits(bits);
414 }
415 }
416 }
417}
418
419impl<const NROWS: usize, const COLS: usize, const PLANES: usize> Default
420 for DmaFrameBuffer<NROWS, COLS, PLANES>
421{
422 fn default() -> Self {
423 Self::new()
424 }
425}
426
427impl<const NROWS: usize, const COLS: usize, const PLANES: usize> core::fmt::Debug
428 for DmaFrameBuffer<NROWS, COLS, PLANES>
429{
430 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
431 f.debug_struct("DmaFrameBuffer")
432 .field("size", &core::mem::size_of_val(&self.planes))
433 .field("plane_count", &self.planes.len())
434 .field("plane_size", &core::mem::size_of_val(&self.planes[0]))
435 .finish()
436 }
437}
438
439#[cfg(feature = "defmt")]
440impl<const NROWS: usize, const COLS: usize, const PLANES: usize> defmt::Format
441 for DmaFrameBuffer<NROWS, COLS, PLANES>
442{
443 fn format(&self, f: defmt::Formatter) {
444 defmt::write!(f, "DmaFrameBuffer<{}, {}, {}>", NROWS, COLS, PLANES);
445 defmt::write!(f, " size: {}", core::mem::size_of_val(&self.planes));
446 defmt::write!(
447 f,
448 " plane_size: {}",
449 core::mem::size_of_val(&self.planes[0])
450 );
451 }
452}
453
454impl<const NROWS: usize, const COLS: usize, const PLANES: usize> FrameBuffer
455 for DmaFrameBuffer<NROWS, COLS, PLANES>
456{
457 type Word = u16;
458
459 fn plane_count(&self) -> usize {
460 PLANES
461 }
462
463 fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
464 assert!(
465 plane_idx < PLANES,
466 "plane_idx {plane_idx} out of range for {PLANES} planes"
467 );
468 let ptr = (&raw const self.planes[plane_idx]).cast::<u8>();
469 let len = core::mem::size_of::<PlaneData<NROWS, COLS>>();
470 (ptr, len)
471 }
472}
473
474impl<const NROWS: usize, const COLS: usize, const PLANES: usize> FrameBufferOperations
475 for DmaFrameBuffer<NROWS, COLS, PLANES>
476{
477 #[inline]
478 fn erase(&mut self) {
479 DmaFrameBuffer::<NROWS, COLS, PLANES>::erase(self);
480 }
481
482 #[inline]
483 fn set_pixel(&mut self, p: Point, color: Color) {
484 DmaFrameBuffer::<NROWS, COLS, PLANES>::set_pixel(self, p, color);
485 }
486}
487
488impl<const NROWS: usize, const COLS: usize, const PLANES: usize> MutableFrameBuffer
489 for DmaFrameBuffer<NROWS, COLS, PLANES>
490{
491}
492
493impl<const NROWS: usize, const COLS: usize, const PLANES: usize> OriginDimensions
494 for DmaFrameBuffer<NROWS, COLS, PLANES>
495{
496 fn size(&self) -> Size {
497 Size::new(COLS as u32, (NROWS * 2) as u32)
498 }
499}
500
501impl<const NROWS: usize, const COLS: usize, const PLANES: usize> DrawTarget
502 for DmaFrameBuffer<NROWS, COLS, PLANES>
503{
504 type Color = Color;
505 type Error = Infallible;
506
507 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
508 where
509 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
510 {
511 for pixel in pixels {
512 self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
513 }
514 Ok(())
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 extern crate std;
521
522 use super::*;
523 use embedded_graphics::prelude::*;
524 use std::format;
525
526 const TEST_COLS: usize = 64;
527 type TestBuffer = DmaFrameBuffer<16, 64, 8>;
528
529 #[test]
530 fn row_format_sets_address_and_control_bits() {
531 let mut row = Row::<TEST_COLS>::new();
532 row.format(4);
533
534 let last_idx = map_index(TEST_COLS - 1);
535 assert_eq!(row.data[last_idx].latch(), true);
536 assert_eq!(row.data[last_idx].addr(), 4);
537
538 let first_idx = map_index(0);
539 assert_eq!(row.data[first_idx].addr(), 4);
540 assert_eq!(row.data[first_idx].latch(), false);
541 }
542
543 #[test]
544 fn format_sets_expected_row_addresses_for_all_rows() {
545 let mut fb = TestBuffer::new();
546 fb.format();
547
548 for plane_idx in 0..8 {
549 for row_idx in 0..16 {
550 let prev_addr = if row_idx == 0 { 15 } else { row_idx - 1 };
551 let last_col = map_index(63);
552 assert_eq!(
553 fb.planes[plane_idx].rows[row_idx].data[last_col].addr(),
554 prev_addr as u16
555 );
556 assert_eq!(
557 fb.planes[plane_idx].rows[row_idx].data[last_col].latch(),
558 true
559 );
560 }
561 }
562 }
563
564 #[test]
565 fn set_pixel_maps_top_half_bits_per_plane() {
566 let mut fb = TestBuffer::new();
567 let color = Color::new(0b1010_0101, 0b0101_1010, 0b1111_0000);
568 fb.set_pixel(Point::new(2, 3), color);
569
570 for plane_idx in 0..8 {
571 let bit = 7 - plane_idx;
572 let entry = fb.planes[plane_idx].rows[3].data[map_index(2)];
573 assert_eq!(entry.red1(), ((color.r() >> bit) & 1) != 0);
574 assert_eq!(entry.grn1(), ((color.g() >> bit) & 1) != 0);
575 assert_eq!(entry.blu1(), ((color.b() >> bit) & 1) != 0);
576 }
577 }
578
579 #[test]
580 fn set_pixel_maps_bottom_half_bits_per_plane() {
581 let mut fb = TestBuffer::new();
582 let color = Color::new(0b1100_0011, 0b0011_1100, 0b1001_0110);
583 fb.set_pixel(Point::new(4, 20), color);
584
585 for plane_idx in 0..8 {
586 let bit = 7 - plane_idx;
587 let entry = fb.planes[plane_idx].rows[4].data[map_index(4)];
588 assert_eq!(entry.red2(), ((color.r() >> bit) & 1) != 0);
589 assert_eq!(entry.grn2(), ((color.g() >> bit) & 1) != 0);
590 assert_eq!(entry.blu2(), ((color.b() >> bit) & 1) != 0);
591 }
592 }
593
594 #[test]
595 fn erase_clears_only_color_bits() {
596 let mut fb = TestBuffer::new();
597 let oe_before = fb.planes[0].rows[0].data[map_index(1)].output_enable();
598 fb.set_pixel(Point::new(0, 0), Color::WHITE);
599 fb.erase();
600
601 for plane in &fb.planes {
602 for row in &plane.rows {
603 for entry in &row.data {
604 assert!(!entry.red1());
605 assert!(!entry.grn1());
606 assert!(!entry.blu1());
607 assert!(!entry.red2());
608 assert!(!entry.grn2());
609 assert!(!entry.blu2());
610 }
611 }
612 }
613
614 assert_eq!(
615 fb.planes[0].rows[0].data[map_index(1)].output_enable(),
616 oe_before
617 );
618 }
619
620 #[test]
621 fn draw_target_iter_sets_pixels() {
622 let mut fb = TestBuffer::new();
623 let pixels = [Pixel(Point::new(1, 1), Color::RED)];
624 let result = fb.draw_iter(pixels);
625 assert!(result.is_ok());
626
627 for plane_idx in 0..8 {
628 let bit = 7 - plane_idx;
629 let entry = fb.planes[plane_idx].rows[1].data[map_index(1)];
630 assert_eq!(entry.red1(), ((Color::RED.r() >> bit) & 1) != 0);
631 assert!(!entry.grn1());
632 assert!(!entry.blu1());
633 }
634 }
635
636 #[test]
637 fn set_pixel_ignores_out_of_bounds_and_negative() {
638 let mut fb = TestBuffer::new();
639 let before = fb.planes;
640 fb.set_pixel(Point::new(-1, 0), Color::WHITE);
641 fb.set_pixel(Point::new(0, -1), Color::WHITE);
642 fb.set_pixel(Point::new(64, 0), Color::WHITE);
643 fb.set_pixel(Point::new(0, 32), Color::WHITE);
644 assert_eq!(fb.planes, before);
645 }
646
647 #[test]
648 fn bcm_chunk_info_for_common_panel() {
649 assert_eq!(TestBuffer::bcm_chunk_count(), 8);
650 assert_eq!(
651 TestBuffer::bcm_chunk_bytes(),
652 core::mem::size_of::<PlaneData<16, 64>>()
653 );
654 }
655
656 #[test]
657 fn frame_buffer_trait_accessors_report_expected_values() {
658 let fb = TestBuffer::new();
659 let as_trait: &dyn FrameBuffer<Word = u16> = &fb;
660 assert_eq!(as_trait.get_word_size(), crate::WordSize::Sixteen);
661 assert_eq!(as_trait.plane_count(), 8);
662
663 let (ptr, len) = as_trait.plane_ptr_len(0);
664 assert_eq!(len, core::mem::size_of::<PlaneData<16, 64>>());
665 assert_eq!(ptr, (&raw const fb.planes[0]).cast::<u8>());
666 }
667
668 #[test]
669 #[should_panic(expected = "out of range")]
670 fn plane_ptr_len_panics_for_invalid_plane() {
671 let fb = TestBuffer::new();
672 let _ = fb.plane_ptr_len(8);
673 }
674
675 #[test]
676 fn origin_dimensions_match_panel_geometry() {
677 let fb = TestBuffer::new();
678 assert_eq!(fb.size(), Size::new(64, 32));
679 }
680
681 #[test]
682 fn debug_impl_includes_shape_information() {
683 let fb = TestBuffer::new();
684 let s = format!("{fb:?}");
685 assert!(s.contains("DmaFrameBuffer"));
686 assert!(s.contains("plane_count"));
687 assert!(s.contains("plane_size"));
688 }
689
690 #[test]
691 fn row_format_sets_expected_blank_and_latch_positions() {
692 let mut row = Row::<TEST_COLS>::new();
693 row.format(4);
694
695 let oe_active = !cfg!(feature = "invert-oe");
696
697 let idx_active = map_index(TRAIL_BLANK_DELAY);
699 assert_eq!(row.data[idx_active].output_enable(), oe_active);
700 assert_eq!(row.data[idx_active].addr(), 4);
701
702 let idx_blank = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
704 assert_eq!(row.data[idx_blank].output_enable(), !oe_active);
705
706 let idx_last = map_index(TEST_COLS - 1);
708 assert!(row.data[idx_last].latch());
709 assert_eq!(row.data[idx_last].addr(), 4);
710 }
711
712 #[test]
713 fn default_constructors_match_new() {
714 let row_default = Row::<TEST_COLS>::default();
715 let row_new = Row::<TEST_COLS>::new();
716 assert_eq!(row_default, row_new);
717
718 let fb_default = TestBuffer::default();
719 let fb_new = TestBuffer::new();
720 assert_eq!(fb_default.planes, fb_new.planes);
721 }
722
723 #[test]
724 fn framebuffer_operations_trait_delegates_correctly() {
725 let mut fb = TestBuffer::new();
726 FrameBufferOperations::set_pixel(&mut fb, Point::new(3, 5), Color::GREEN);
727
728 assert!(fb.planes[0].rows[5].data[map_index(3)].grn1());
729
730 FrameBufferOperations::erase(&mut fb);
731 for plane in &fb.planes {
732 for row in &plane.rows {
733 for entry in &row.data {
734 assert!(!entry.red1());
735 assert!(!entry.grn1());
736 assert!(!entry.blu1());
737 assert!(!entry.red2());
738 assert!(!entry.grn2());
739 assert!(!entry.blu2());
740 }
741 }
742 }
743 }
744
745 #[test]
746 fn entry_debug_shows_hex_value() {
747 let mut entry = Entry::new();
748 entry.set_red1(true);
749 let s = format!("{entry:?}");
750 assert!(s.contains("Entry"));
751 assert!(s.contains("0x"));
752 }
753
754 #[test]
755 fn entry_new_oe_matches_feature() {
756 let entry = Entry::new();
757 if cfg!(feature = "invert-oe") {
758 assert!(entry.output_enable());
759 } else {
760 assert!(!entry.output_enable());
761 }
762 }
763
764 #[test]
765 fn make_data_template_oe_polarity() {
766 let mut row = Row::<TEST_COLS>::new();
767 row.format(4);
768
769 let active_idx = map_index(TRAIL_BLANK_DELAY);
770 let blank_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
771 let latch_idx = map_index(TEST_COLS - 1);
772
773 if cfg!(feature = "invert-oe") {
774 assert!(!row.data[active_idx].output_enable());
775 assert!(row.data[blank_idx].output_enable());
776 assert!(row.data[latch_idx].output_enable());
777 } else {
778 assert!(row.data[active_idx].output_enable());
779 assert!(!row.data[blank_idx].output_enable());
780 assert!(!row.data[latch_idx].output_enable());
781 }
782 }
783
784 static STATIC_FB: TestBuffer = TestBuffer::new();
785
786 #[test]
787 fn test_static_construction_is_formatted() {
788 let runtime_fb = TestBuffer::new();
789
790 for (pi, plane) in STATIC_FB.planes.iter().enumerate() {
791 for (ri, row) in plane.rows.iter().enumerate() {
792 assert_eq!(
793 row.data, runtime_fb.planes[pi].rows[ri].data,
794 "static vs runtime mismatch at plane {pi}, row {ri}"
795 );
796 assert_eq!(
797 row.gap, runtime_fb.planes[pi].rows[ri].gap,
798 "static vs runtime gap mismatch at plane {pi}, row {ri}"
799 );
800 }
801 }
802 }
803
804 #[test]
805 fn test_format_reinitializes_at_runtime() {
806 let mut fb = TestBuffer::new();
807 fb.erase();
808 fb.format();
809
810 for (pi, plane) in fb.planes.iter().enumerate() {
811 for (ri, row) in plane.rows.iter().enumerate() {
812 assert_eq!(
813 row.data, STATIC_FB.planes[pi].rows[ri].data,
814 "re-formatted vs static mismatch at plane {pi}, row {ri}"
815 );
816 }
817 }
818 }
819}