Skip to main content

hub75_framebuffer/
plain.rs

1//! DMA-friendly framebuffer implementation for HUB75 LED panels.
2//!
3//! This module provides a framebuffer implementation with memory
4//! layout optimized for efficient transfer to HUB75 LED panels. The data is
5//! structured for direct signal mapping, making it ideal for DMA transfers but
6//! also suitable for programmatic transfer. It supports RGB color and brightness
7//! control through multiple frames using Binary Code Modulation (BCM).
8//!
9//! # Hardware Requirements
10//! This implementation can be used by any microcontroller that has a peripheral
11//! capable of outputting a clock signal and 16 bits (though only 14 bits are
12//! actually needed) in parallel. The data is structured to directly match the
13//! HUB75 connector signals, making it suitable for various parallel output
14//! peripherals.
15//!
16//! # Features
17//! - Memory layout optimized for efficient data transfer
18//! - Direct signal mapping to HUB75 connector signals
19//! - Support for RGB color with brightness control
20//! - Multiple frame buffers for Binary Code Modulation (BCM)
21//! - Integration with embedded-graphics for easy drawing
22//!
23//! # Brightness Control
24//! Brightness is controlled through Binary Code Modulation (BCM):
25//! - The number of brightness levels is determined by the `BITS` parameter
26//! - Each additional bit doubles the number of brightness levels
27//! - More bits provide better brightness resolution but require more memory
28//! - Memory usage grows exponentially with the number of bits: `(2^BITS)-1`
29//!   frames
30//! - Example: 8 bits = 256 levels, 4 bits = 16 levels
31//!
32//! # Memory Usage
33//! The framebuffer's memory usage is determined by:
34//! - Panel size (ROWS × COLS)
35//! - Number of brightness bits (BITS)
36//! - Memory grows exponentially with bits: `(2^BITS)-1` frames
37//! - 16-bit entries provide direct signal mapping but use more memory
38//!
39//! # Example
40//! ```rust,no_run
41//! use embedded_graphics::pixelcolor::RgbColor;
42//! use embedded_graphics::prelude::*;
43//! use embedded_graphics::primitives::Circle;
44//! use embedded_graphics::primitives::Rectangle;
45//! use hub75_framebuffer::compute_frame_count;
46//! use hub75_framebuffer::compute_rows;
47//! use hub75_framebuffer::Color;
48//! use hub75_framebuffer::plain::DmaFrameBuffer;
49//! use embedded_graphics::primitives::PrimitiveStyle;
50//!
51//! // Create a framebuffer for a 64x32 panel with 3-bit color depth
52//! const ROWS: usize = 32;
53//! const COLS: usize = 64;
54//! const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
55//! const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
56//! const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
57//!
58//! let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
59//!
60//! // Draw a red rectangle
61//! Rectangle::new(Point::new(10, 10), Size::new(20, 20))
62//!     .into_styled(PrimitiveStyle::with_fill(Color::RED))
63//!     .draw(&mut framebuffer)
64//!     .unwrap();
65//!
66//! // Draw a blue circle
67//! Circle::new(Point::new(40, 20), 10)
68//!     .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
69//!     .draw(&mut framebuffer)
70//!     .unwrap();
71//! ```
72//!
73//! # Implementation Details
74//! The framebuffer is organized to directly match the HUB75 connector signals:
75//! - Each 16-bit word maps directly to the HUB75 control signals
76//! - Color data (R, G, B) for two sub-pixels is stored in dedicated bits
77//! - Control signals (output enable, latch, address) are mapped to specific
78//!   bits
79//! - Multiple frames are used to achieve Binary Code Modulation (BCM)
80//! - DMA transfers the data directly to the panel without any transformation
81//!
82//! # HUB75 Signal Bit Mapping
83//! Each 16-bit `Entry` represents the logic levels that will be driven onto the HUB75
84//! bus during a single pixel-clock cycle. Every panel signal—apart from the clock
85//! (`CLK`) itself—occupies a dedicated bit, allowing the word to be streamed via DMA
86//! with zero run-time manipulation.
87//!
88//! ```text
89//! 15 ─ Dummy2   (spare)
90//! 14 ─ B2       Blue  – lower half of the panel
91//! 13 ─ G2       Green – lower half of the panel
92//! 12 ─ R2       Red   – lower half of the panel
93//! 11 ─ B1       Blue  – upper half of the panel
94//! 10 ─ G1       Green – upper half of the panel
95//!  9 ─ R1       Red   – upper half of the panel
96//!  8 ─ OE       Output-Enable / Blank
97//!  7 ─ Dummy1   (spare)
98//!  6 ─ Dummy0   (spare)
99//!  5 ─ LAT      Latch / STB
100//! 4-0 ─ A..E    Row address lines
101//! ```
102//!
103//! The pixel clock is generated by the peripheral that owns the DMA stream and
104//! is therefore **not** part of the 16-bit word stored in the framebuffer.
105//!
106//! # Binary Code Modulation (BCM) Frames
107//! Brightness is achieved with Binary-Code-Modulation as outlined in
108//! <https://www.batsocks.co.uk/readme/art_bcm_1.htm>. For a colour depth of
109//! `BITS`, the driver allocates `FRAME_COUNT = 2^BITS - 1` frames. Frame *n*
110//! (0-based) is displayed for a duration proportional to `2^n`, mirroring the
111//! weight of that bit in a binary number.
112//!
113//! When a pixel is written its 8-bit RGB components are compared against a
114//! per-frame threshold:
115//!
116//! ```text
117//! brightness_step = 256 / 2^BITS
118//! threshold_n     = (n + 1) * brightness_step
119//! ```
120//!
121//! If a channel's value is greater than or equal to `threshold_n` the
122//! corresponding colour bit is set in frame *n*. Streaming the frames from the
123//! least-significant (shortest) to the most-significant (longest) time-slot
124//! produces the correct 8-bit brightness while keeping the refresh routine
125//! trivial.
126//!
127//! # Safety
128//! This implementation uses unsafe code for DMA operations. The framebuffer
129//! must be properly aligned in memory and the DMA configuration must match the
130//! buffer layout.
131
132use core::convert::Infallible;
133
134use crate::{FrameBufferOperations, MutableFrameBuffer};
135use bitfield::bitfield;
136use embedded_dma::ReadBuffer;
137use embedded_graphics::pixelcolor::RgbColor;
138use embedded_graphics::prelude::Point;
139
140use super::Color;
141use super::FrameBuffer;
142use super::WordSize;
143
144#[cfg(feature = "blank-delay-1")]
145const BLANKING_DELAY: usize = 1;
146#[cfg(feature = "blank-delay-2")]
147const BLANKING_DELAY: usize = 2;
148#[cfg(feature = "blank-delay-4")]
149const BLANKING_DELAY: usize = 4;
150#[cfg(feature = "blank-delay-8")]
151const BLANKING_DELAY: usize = 8;
152
153// Default to 1 if no blanking delay feature is enabled
154#[cfg(not(any(
155    feature = "blank-delay-1",
156    feature = "blank-delay-2",
157    feature = "blank-delay-4",
158    feature = "blank-delay-8"
159)))]
160const BLANKING_DELAY: usize = 1;
161
162/// Creates a pre-computed data template for a row with the specified addresses.
163/// This template contains all the timing and control signals but no pixel data.
164#[inline]
165const fn make_data_template<const COLS: usize>(addr: u8, prev_addr: u8) -> [Entry; COLS] {
166    let mut data = [Entry::new(); COLS];
167    let mut i = 0;
168
169    while i < COLS {
170        let mut entry = Entry::new();
171        entry.0 = prev_addr as u16;
172
173        // Apply timing control based on position
174        if i == 1 {
175            entry.0 |= 0b1_0000_0000; // set output_enable bit
176        } else if i == COLS - BLANKING_DELAY - 1 {
177            // output_enable already false from initialization
178        } else if i == COLS - 1 {
179            entry.0 |= 0b0010_0000; // set latch bit
180            entry.0 = (entry.0 & !0b0001_1111) | (addr as u16); // set new address
181        } else if i > 1 && i < COLS - BLANKING_DELAY - 1 {
182            entry.0 |= 0b1_0000_0000; // set output_enable bit
183        }
184
185        data[map_index(i)] = entry;
186        i += 1;
187    }
188
189    data
190}
191
192bitfield! {
193    /// A 16-bit word representing the HUB75 control signals for a single pixel.
194    ///
195    /// This structure directly maps to the HUB75 connector signals:
196    /// - RGB color data for two sub-pixels (color0 and color1)
197    /// - Panel control signals (output enable, latch, address)
198    /// - Dummy bits for timing alignment
199    ///
200    /// The bit layout matches the HUB75 connector signals:
201    /// - Bit 15: Dummy bit 2
202    /// - Bit 14: Blue channel for color1
203    /// - Bit 13: Green channel for color1
204    /// - Bit 12: Red channel for color1
205    /// - Bit 11: Blue channel for color0
206    /// - Bit 10: Green channel for color0
207    /// - Bit 9: Red channel for color0
208    /// - Bit 8: Output enable
209    /// - Bit 7: Dummy bit 1
210    /// - Bit 6: Dummy bit 0
211    /// - Bit 5: Latch signal
212    /// - Bits 4-0: Row address
213    #[derive(Clone, Copy, Default, PartialEq)]
214    #[repr(transparent)]
215    struct Entry(u16);
216    dummy2, set_dummy2: 15;
217    blu2, set_blu2: 14;
218    grn2, set_grn2: 13;
219    red2, set_red2: 12;
220    blu1, set_blu1: 11;
221    grn1, set_grn1: 10;
222    red1, set_red1: 9;
223    output_enable, set_output_enable: 8;
224    dummy1, set_dummy1: 7;
225    dummy0, set_dummy0: 6;
226    latch, set_latch: 5;
227    addr, set_addr: 4, 0;
228}
229
230impl core::fmt::Debug for Entry {
231    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
232        f.debug_tuple("Entry")
233            .field(&format_args!("{:#x}", self.0))
234            .finish()
235    }
236}
237
238#[cfg(feature = "defmt")]
239impl defmt::Format for Entry {
240    fn format(&self, f: defmt::Formatter) {
241        defmt::write!(f, "Entry({=u16:#x})", self.0);
242    }
243}
244
245impl Entry {
246    const fn new() -> Self {
247        Self(0)
248    }
249
250    // Optimized color bit manipulation constants and methods
251    const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; // bits 9-11: R1, G1, B1
252    const COLOR1_MASK: u16 = 0b0111_0000_0000_0000; // bits 12-14: R2, G2, B2
253
254    #[inline]
255    fn set_color0_bits(&mut self, bits: u8) {
256        let bits16 = u16::from(bits) << 9;
257        self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
258    }
259
260    #[inline]
261    fn set_color1_bits(&mut self, bits: u8) {
262        let bits16 = u16::from(bits) << 12;
263        self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
264    }
265}
266
267/// Represents a single row of pixels in the framebuffer.
268///
269/// Each row contains a fixed number of columns (`COLS`) and manages the timing
270/// and control signals for the HUB75 panel. The row handles:
271/// - Output enable timing to prevent ghosting
272/// - Latch signal generation for row updates
273/// - Row address management
274/// - Color data for both sub-pixels
275#[derive(Clone, Copy, PartialEq, Debug)]
276#[repr(C)]
277struct Row<const COLS: usize> {
278    data: [Entry; COLS],
279}
280
281const fn map_index(i: usize) -> usize {
282    #[cfg(feature = "esp32-ordering")]
283    {
284        i ^ 1
285    }
286    #[cfg(not(feature = "esp32-ordering"))]
287    {
288        i
289    }
290}
291
292impl<const COLS: usize> Default for Row<COLS> {
293    fn default() -> Self {
294        Self::new()
295    }
296}
297
298impl<const COLS: usize> Row<COLS> {
299    pub const fn new() -> Self {
300        Self {
301            data: [Entry::new(); COLS],
302        }
303    }
304
305    pub fn format(&mut self, addr: u8, prev_addr: u8) {
306        // Use pre-computed template and bulk copy for maximum performance
307        let template = make_data_template::<COLS>(addr, prev_addr);
308        self.data.copy_from_slice(&template);
309    }
310
311    /// Fast clear method that preserves timing/control bits while clearing pixel data.
312    /// Uses bulk memory operations for maximum performance.
313    #[inline]
314    pub fn clear_colors(&mut self) {
315        // Clear color bits while preserving timing and control bits
316        const COLOR_CLEAR_MASK: u16 = !0b0111_1110_0000_0000; // Clear bits 9-14 (R1,G1,B1,R2,G2,B2)
317
318        for entry in &mut self.data {
319            entry.0 &= COLOR_CLEAR_MASK;
320        }
321    }
322
323    #[inline]
324    pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
325        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
326        let col = map_index(col);
327        self.data[col].set_color0_bits(bits);
328    }
329
330    #[inline]
331    pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
332        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
333        let col = map_index(col);
334        self.data[col].set_color1_bits(bits);
335    }
336}
337
338#[derive(Copy, Clone, Debug)]
339#[repr(C)]
340struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
341    rows: [Row<COLS>; NROWS],
342}
343
344impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
345    pub const fn new() -> Self {
346        Self {
347            rows: [Row::new(); NROWS],
348        }
349    }
350
351    pub fn format(&mut self) {
352        for (addr, row) in self.rows.iter_mut().enumerate() {
353            let prev_addr = if addr == 0 {
354                NROWS as u8 - 1
355            } else {
356                addr as u8 - 1
357            };
358            row.format(addr as u8, prev_addr);
359        }
360    }
361
362    /// Fast clear method that preserves timing/control bits while clearing pixel data.
363    #[inline]
364    pub fn clear_colors(&mut self) {
365        for row in &mut self.rows {
366            row.clear_colors();
367        }
368    }
369
370    #[inline]
371    pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
372        let row = &mut self.rows[if y < NROWS { y } else { y - NROWS }];
373        if y < NROWS {
374            row.set_color0(x, red, green, blue);
375        } else {
376            row.set_color1(x, red, green, blue);
377        }
378    }
379}
380
381impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
382    for Frame<ROWS, COLS, NROWS>
383{
384    fn default() -> Self {
385        Self::new()
386    }
387}
388
389/// DMA-compatible framebuffer for HUB75 LED panels.
390///
391/// This is a framebuffer implementation that:
392/// - Manages multiple frames for Binary Code Modulation (BCM)
393/// - Provides DMA-compatible memory layout
394/// - Implements the embedded-graphics `DrawTarget` trait
395///
396/// # Type Parameters
397/// - `ROWS`: Total number of rows in the panel
398/// - `COLS`: Number of columns in the panel
399/// - `NROWS`: Number of rows per scan (typically half of ROWS)
400/// - `BITS`: Color depth (1-8 bits)
401/// - `FRAME_COUNT`: Number of frames used for Binary Code Modulation
402///
403/// # Helper Functions
404/// Use these functions to compute the correct values:
405/// - `esp_hub75::compute_frame_count(BITS)`: Computes the required number of
406///   frames
407/// - `esp_hub75::compute_rows(ROWS)`: Computes the number of rows per scan
408///
409/// # Memory Layout
410/// The buffer is aligned to ensure efficient DMA transfers and contains:
411/// - A 64-bit alignment field
412/// - An array of frames, each containing the full panel data
413#[derive(Copy, Clone)]
414#[repr(C)]
415pub struct DmaFrameBuffer<
416    const ROWS: usize,
417    const COLS: usize,
418    const NROWS: usize,
419    const BITS: u8,
420    const FRAME_COUNT: usize,
421> {
422    _align: u64,
423    frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
424}
425
426impl<
427        const ROWS: usize,
428        const COLS: usize,
429        const NROWS: usize,
430        const BITS: u8,
431        const FRAME_COUNT: usize,
432    > Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
433{
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439impl<
440        const ROWS: usize,
441        const COLS: usize,
442        const NROWS: usize,
443        const BITS: u8,
444        const FRAME_COUNT: usize,
445    > DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
446{
447    /// Create a new, ready-to-use framebuffer.
448    ///
449    /// This creates a new framebuffer and automatically formats it with proper timing signals.
450    /// The framebuffer is immediately ready for pixel operations and DMA transfers.
451    ///
452    /// # Panics
453    ///
454    /// Panics if `BITS` is greater than 8, as only 1-8 bit color depths are supported.
455    ///
456    /// # Example
457    /// ```rust,no_run
458    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
459    ///
460    /// const ROWS: usize = 32;
461    /// const COLS: usize = 64;
462    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
463    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
464    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
465    ///
466    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
467    /// // No need to call format() - framebuffer is ready to use!
468    /// ```
469    #[must_use]
470    pub fn new() -> Self {
471        debug_assert!(BITS <= 8);
472
473        let mut instance = Self {
474            _align: 0,
475            frames: [Frame::new(); FRAME_COUNT],
476        };
477
478        // Pre-format the framebuffer so it's immediately ready for use
479        instance.format();
480        instance
481    }
482
483    /// Returns the number of BCM chunks in this framebuffer (always 1 for
484    /// single-plane framebuffers — the entire buffer is one contiguous chunk).
485    #[must_use]
486    pub const fn bcm_chunk_count() -> usize {
487        1
488    }
489
490    /// Returns the byte size of one BCM chunk (for single-plane framebuffers
491    /// this equals the total DMA buffer size, since BCM weighting is baked in).
492    #[must_use]
493    pub const fn bcm_chunk_bytes() -> usize {
494        core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
495    }
496
497    /// Perform full formatting of the framebuffer with timing and control signals.
498    ///
499    /// This sets up all the timing and control signals needed for proper HUB75 operation.
500    /// This is automatically called by `new()`, so you typically don't need to call this
501    /// unless you want to completely reinitialize the framebuffer.
502    ///
503    /// # Example
504    /// ```rust,no_run
505    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
506    ///
507    /// const ROWS: usize = 32;
508    /// const COLS: usize = 64;
509    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
510    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
511    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
512    ///
513    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
514    /// framebuffer.format(); // Reinitialize if needed
515    /// ```
516    #[inline]
517    pub fn format(&mut self) {
518        for frame in &mut self.frames {
519            frame.format();
520        }
521    }
522
523    /// Fast erase operation that clears all pixel data while preserving timing signals.
524    ///
525    /// This is much faster than `format()` when you just want to clear the display
526    /// since it preserves all the timing and control signals that are already set up.
527    /// Use this for clearing between frames or when you want to start drawing fresh content.
528    ///
529    /// # Example
530    /// ```rust,no_run
531    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
532    ///
533    /// const ROWS: usize = 32;
534    /// const COLS: usize = 64;
535    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
536    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
537    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
538    ///
539    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
540    /// framebuffer.erase();
541    /// ```
542    #[inline]
543    pub fn erase(&mut self) {
544        for frame in &mut self.frames {
545            frame.clear_colors();
546        }
547    }
548
549    /// Set a pixel in the framebuffer.
550    /// # Example
551    /// ```rust,no_run
552    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
553    /// use embedded_graphics::prelude::*;
554    ///
555    /// const ROWS: usize = 32;
556    /// const COLS: usize = 64;
557    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
558    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
559    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
560    ///
561    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
562    /// framebuffer.set_pixel(Point::new(10, 10), Color::RED);
563    /// ```
564    pub fn set_pixel(&mut self, p: Point, color: Color) {
565        if p.x < 0 || p.y < 0 {
566            return;
567        }
568        self.set_pixel_internal(p.x as usize, p.y as usize, color);
569    }
570
571    #[inline]
572    fn frames_on(v: u8) -> usize {
573        // v / brightness_step but the compiler resolves the shift at build-time
574        (v as usize) >> (8 - BITS)
575    }
576
577    #[inline]
578    fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
579        if x >= COLS || y >= ROWS {
580            return;
581        }
582
583        // Early exit for black pixels - common in UI backgrounds
584        // Only enabled when skip-black-pixels feature is active
585        #[cfg(feature = "skip-black-pixels")]
586        if color == Color::BLACK {
587            return;
588        }
589
590        // Pre-compute how many frames each channel should be on
591        let red_frames = Self::frames_on(color.r());
592        let green_frames = Self::frames_on(color.g());
593        let blue_frames = Self::frames_on(color.b());
594
595        // Set the pixel in all frames based on pre-computed frame counts
596        for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
597            frame.set_pixel(
598                y,
599                x,
600                frame_idx < red_frames,
601                frame_idx < green_frames,
602                frame_idx < blue_frames,
603            );
604        }
605    }
606}
607
608impl<
609        const ROWS: usize,
610        const COLS: usize,
611        const NROWS: usize,
612        const BITS: u8,
613        const FRAME_COUNT: usize,
614    > FrameBufferOperations for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
615{
616    #[inline]
617    fn erase(&mut self) {
618        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
619    }
620
621    #[inline]
622    fn set_pixel(&mut self, p: Point, color: Color) {
623        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
624    }
625}
626
627impl<
628        const ROWS: usize,
629        const COLS: usize,
630        const NROWS: usize,
631        const BITS: u8,
632        const FRAME_COUNT: usize,
633    > embedded_graphics::prelude::OriginDimensions
634    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
635{
636    fn size(&self) -> embedded_graphics::prelude::Size {
637        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
638    }
639}
640
641impl<
642        const ROWS: usize,
643        const COLS: usize,
644        const NROWS: usize,
645        const BITS: u8,
646        const FRAME_COUNT: usize,
647    > embedded_graphics::prelude::OriginDimensions
648    for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
649{
650    fn size(&self) -> embedded_graphics::prelude::Size {
651        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
652    }
653}
654
655impl<
656        const ROWS: usize,
657        const COLS: usize,
658        const NROWS: usize,
659        const BITS: u8,
660        const FRAME_COUNT: usize,
661    > embedded_graphics::draw_target::DrawTarget
662    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
663{
664    type Color = Color;
665
666    type Error = Infallible;
667
668    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
669    where
670        I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
671    {
672        for pixel in pixels {
673            self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
674        }
675        Ok(())
676    }
677}
678
679unsafe impl<
680        const ROWS: usize,
681        const COLS: usize,
682        const NROWS: usize,
683        const BITS: u8,
684        const FRAME_COUNT: usize,
685    > ReadBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
686{
687    type Word = u8;
688
689    unsafe fn read_buffer(&self) -> (*const u8, usize) {
690        let ptr = (&raw const self.frames).cast::<u8>();
691        let len = core::mem::size_of_val(&self.frames);
692        (ptr, len)
693    }
694}
695
696unsafe impl<
697        const ROWS: usize,
698        const COLS: usize,
699        const NROWS: usize,
700        const BITS: u8,
701        const FRAME_COUNT: usize,
702    > ReadBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
703{
704    type Word = u8;
705
706    unsafe fn read_buffer(&self) -> (*const u8, usize) {
707        let ptr = (&raw const self.frames).cast::<u8>();
708        let len = core::mem::size_of_val(&self.frames);
709        (ptr, len)
710    }
711}
712
713impl<
714        const ROWS: usize,
715        const COLS: usize,
716        const NROWS: usize,
717        const BITS: u8,
718        const FRAME_COUNT: usize,
719    > core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
720{
721    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
722        let brightness_step = 1 << (8 - BITS);
723        f.debug_struct("DmaFrameBuffer")
724            .field("size", &core::mem::size_of_val(&self.frames))
725            .field("frame_count", &self.frames.len())
726            .field("frame_size", &core::mem::size_of_val(&self.frames[0]))
727            .field("brightness_step", &&brightness_step)
728            .finish_non_exhaustive()
729    }
730}
731
732#[cfg(feature = "defmt")]
733impl<
734        const ROWS: usize,
735        const COLS: usize,
736        const NROWS: usize,
737        const BITS: u8,
738        const FRAME_COUNT: usize,
739    > defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
740{
741    fn format(&self, f: defmt::Formatter) {
742        let brightness_step = 1 << (8 - BITS);
743        defmt::write!(
744            f,
745            "DmaFrameBuffer<{}, {}, {}, {}, {}>",
746            ROWS,
747            COLS,
748            NROWS,
749            BITS,
750            FRAME_COUNT
751        );
752        defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
753        defmt::write!(
754            f,
755            " frame_size: {}",
756            core::mem::size_of_val(&self.frames[0])
757        );
758        defmt::write!(f, " brightness_step: {}", brightness_step);
759    }
760}
761
762impl<
763        const ROWS: usize,
764        const COLS: usize,
765        const NROWS: usize,
766        const BITS: u8,
767        const FRAME_COUNT: usize,
768    > FrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
769{
770    fn get_word_size(&self) -> WordSize {
771        WordSize::Sixteen
772    }
773
774    fn plane_count(&self) -> usize {
775        1
776    }
777
778    fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
779        assert!(plane_idx == 0, "plain DmaFrameBuffer has only 1 plane");
780        let ptr = (&raw const self.frames).cast::<u8>();
781        let len = core::mem::size_of_val(&self.frames);
782        (ptr, len)
783    }
784}
785
786impl<
787        const ROWS: usize,
788        const COLS: usize,
789        const NROWS: usize,
790        const BITS: u8,
791        const FRAME_COUNT: usize,
792    > FrameBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
793{
794    fn get_word_size(&self) -> WordSize {
795        WordSize::Sixteen
796    }
797
798    fn plane_count(&self) -> usize {
799        1
800    }
801
802    fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
803        assert!(plane_idx == 0, "plain DmaFrameBuffer has only 1 plane");
804        let ptr = (&raw const self.frames).cast::<u8>();
805        let len = core::mem::size_of_val(&self.frames);
806        (ptr, len)
807    }
808}
809
810impl<
811        const ROWS: usize,
812        const COLS: usize,
813        const NROWS: usize,
814        const BITS: u8,
815        const FRAME_COUNT: usize,
816    > MutableFrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
817{
818}
819
820#[cfg(test)]
821mod tests {
822    extern crate std;
823
824    use std::format;
825    use std::vec;
826
827    use super::*;
828    use crate::{FrameBuffer, WordSize};
829    use embedded_graphics::pixelcolor::RgbColor;
830    use embedded_graphics::prelude::*;
831    use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
832
833    const TEST_ROWS: usize = 32;
834    const TEST_COLS: usize = 64;
835    const TEST_NROWS: usize = TEST_ROWS / 2;
836    const TEST_BITS: u8 = 3;
837    const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1; // 7 frames for 3-bit depth
838
839    type TestFrameBuffer =
840        DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
841
842    // Helper function to get mapped index for ESP32
843    fn get_mapped_index(index: usize) -> usize {
844        map_index(index)
845    }
846
847    #[test]
848    fn test_entry_construction() {
849        let entry = Entry::new();
850        assert_eq!(entry.0, 0);
851        assert_eq!(entry.dummy2(), false);
852        assert_eq!(entry.blu2(), false);
853        assert_eq!(entry.grn2(), false);
854        assert_eq!(entry.red2(), false);
855        assert_eq!(entry.blu1(), false);
856        assert_eq!(entry.grn1(), false);
857        assert_eq!(entry.red1(), false);
858        assert_eq!(entry.output_enable(), false);
859        assert_eq!(entry.dummy1(), false);
860        assert_eq!(entry.dummy0(), false);
861        assert_eq!(entry.latch(), false);
862        assert_eq!(entry.addr(), 0);
863    }
864
865    #[test]
866    fn test_entry_setters() {
867        let mut entry = Entry::new();
868
869        entry.set_dummy2(true);
870        assert_eq!(entry.dummy2(), true);
871        assert_eq!(entry.0 & 0b1000000000000000, 0b1000000000000000);
872
873        entry.set_blu2(true);
874        assert_eq!(entry.blu2(), true);
875        assert_eq!(entry.0 & 0b0100000000000000, 0b0100000000000000);
876
877        entry.set_grn2(true);
878        assert_eq!(entry.grn2(), true);
879        assert_eq!(entry.0 & 0b0010000000000000, 0b0010000000000000);
880
881        entry.set_red2(true);
882        assert_eq!(entry.red2(), true);
883        assert_eq!(entry.0 & 0b0001000000000000, 0b0001000000000000);
884
885        entry.set_blu1(true);
886        assert_eq!(entry.blu1(), true);
887        assert_eq!(entry.0 & 0b0000100000000000, 0b0000100000000000);
888
889        entry.set_grn1(true);
890        assert_eq!(entry.grn1(), true);
891        assert_eq!(entry.0 & 0b0000010000000000, 0b0000010000000000);
892
893        entry.set_red1(true);
894        assert_eq!(entry.red1(), true);
895        assert_eq!(entry.0 & 0b0000001000000000, 0b0000001000000000);
896
897        entry.set_output_enable(true);
898        assert_eq!(entry.output_enable(), true);
899        assert_eq!(entry.0 & 0b0000000100000000, 0b0000000100000000);
900
901        entry.set_dummy1(true);
902        assert_eq!(entry.dummy1(), true);
903        assert_eq!(entry.0 & 0b0000000010000000, 0b0000000010000000);
904
905        entry.set_dummy0(true);
906        assert_eq!(entry.dummy0(), true);
907        assert_eq!(entry.0 & 0b0000000001000000, 0b0000000001000000);
908
909        entry.set_latch(true);
910        assert_eq!(entry.latch(), true);
911        assert_eq!(entry.0 & 0b0000000000100000, 0b0000000000100000);
912
913        entry.set_addr(0b11111);
914        assert_eq!(entry.addr(), 0b11111);
915        assert_eq!(entry.0 & 0b0000000000011111, 0b0000000000011111);
916    }
917
918    #[test]
919    fn test_entry_bit_isolation() {
920        let mut entry = Entry::new();
921
922        // Test that setting one field doesn't affect others
923        entry.set_addr(0b11111);
924        entry.set_latch(true);
925        assert_eq!(entry.addr(), 0b11111);
926        assert_eq!(entry.latch(), true);
927        assert_eq!(entry.output_enable(), false);
928        assert_eq!(entry.red1(), false);
929
930        entry.set_red1(true);
931        entry.set_grn2(true);
932        assert_eq!(entry.addr(), 0b11111);
933        assert_eq!(entry.latch(), true);
934        assert_eq!(entry.red1(), true);
935        assert_eq!(entry.grn2(), true);
936        assert_eq!(entry.blu1(), false);
937        assert_eq!(entry.red2(), false);
938    }
939
940    #[test]
941    fn test_entry_set_color0() {
942        let mut entry = Entry::new();
943
944        let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); // b=1, g=0, r=1 = 0b101
945        entry.set_color0_bits(bits);
946        assert_eq!(entry.red1(), true);
947        assert_eq!(entry.grn1(), false);
948        assert_eq!(entry.blu1(), true);
949        // Check that only the expected bits are set
950        assert_eq!(entry.0 & 0b0000101000000000, 0b0000101000000000); // Red1 and Blue1 bits
951    }
952
953    #[test]
954    fn test_entry_set_color1() {
955        let mut entry = Entry::new();
956
957        let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); // b=1, g=1, r=0 = 0b110
958        entry.set_color1_bits(bits);
959        assert_eq!(entry.red2(), false);
960        assert_eq!(entry.grn2(), true);
961        assert_eq!(entry.blu2(), true);
962        // Check that only the expected bits are set
963        assert_eq!(entry.0 & 0b0110000000000000, 0b0110000000000000); // Green2 and Blue2 bits
964    }
965
966    #[test]
967    fn test_entry_debug_formatting() {
968        let entry = Entry(0x1234);
969        let debug_str = format!("{:?}", entry);
970        assert_eq!(debug_str, "Entry(0x1234)");
971
972        let entry = Entry(0xabcd);
973        let debug_str = format!("{:?}", entry);
974        assert_eq!(debug_str, "Entry(0xabcd)");
975    }
976
977    #[test]
978    fn test_row_construction() {
979        let row: Row<TEST_COLS> = Row::new();
980        assert_eq!(row.data.len(), TEST_COLS);
981
982        // Check that all entries are initialized to zero
983        for entry in &row.data {
984            assert_eq!(entry.0, 0);
985        }
986    }
987
988    #[test]
989    fn test_row_format() {
990        let mut row: Row<TEST_COLS> = Row::new();
991        let test_addr = 5;
992        let prev_addr = 4;
993
994        row.format(test_addr, prev_addr);
995
996        // Check data entries configuration
997        for (physical_i, entry) in row.data.iter().enumerate() {
998            let logical_i = get_mapped_index(physical_i);
999
1000            match logical_i {
1001                i if i == TEST_COLS - BLANKING_DELAY - 1 => {
1002                    // Second to last pixel should have output_enable false
1003                    assert_eq!(entry.output_enable(), false);
1004                    assert_eq!(entry.addr(), prev_addr as u16);
1005                    assert_eq!(entry.latch(), false);
1006                }
1007                i if i == TEST_COLS - 1 => {
1008                    // Last pixel should have latch true and new address
1009                    assert_eq!(entry.latch(), true);
1010                    assert_eq!(entry.addr(), test_addr as u16);
1011                    assert_eq!(entry.output_enable(), false);
1012                }
1013                1 => {
1014                    // First pixel after start should have output_enable true
1015                    assert_eq!(entry.output_enable(), true);
1016                    assert_eq!(entry.addr(), prev_addr as u16);
1017                    assert_eq!(entry.latch(), false);
1018                }
1019                _ => {
1020                    // Other pixels should have the previous address and no latch
1021                    assert_eq!(entry.addr(), prev_addr as u16);
1022                    assert_eq!(entry.latch(), false);
1023                    if logical_i > 1 && logical_i < TEST_COLS - BLANKING_DELAY - 1 {
1024                        assert_eq!(entry.output_enable(), true);
1025                    }
1026                }
1027            }
1028        }
1029    }
1030
1031    #[test]
1032    fn test_row_set_color0() {
1033        let mut row: Row<TEST_COLS> = Row::new();
1034
1035        row.set_color0(0, true, false, true);
1036
1037        let mapped_col_0 = get_mapped_index(0);
1038        assert_eq!(row.data[mapped_col_0].red1(), true);
1039        assert_eq!(row.data[mapped_col_0].grn1(), false);
1040        assert_eq!(row.data[mapped_col_0].blu1(), true);
1041
1042        // Test another column
1043        row.set_color0(1, false, true, false);
1044
1045        let mapped_col_1 = get_mapped_index(1);
1046        assert_eq!(row.data[mapped_col_1].red1(), false);
1047        assert_eq!(row.data[mapped_col_1].grn1(), true);
1048        assert_eq!(row.data[mapped_col_1].blu1(), false);
1049    }
1050
1051    #[test]
1052    fn test_row_set_color1() {
1053        let mut row: Row<TEST_COLS> = Row::new();
1054
1055        row.set_color1(0, true, true, false);
1056
1057        let mapped_col_0 = get_mapped_index(0);
1058        assert_eq!(row.data[mapped_col_0].red2(), true);
1059        assert_eq!(row.data[mapped_col_0].grn2(), true);
1060        assert_eq!(row.data[mapped_col_0].blu2(), false);
1061    }
1062
1063    #[test]
1064    fn test_row_default() {
1065        let row1: Row<TEST_COLS> = Row::new();
1066        let row2: Row<TEST_COLS> = Row::default();
1067
1068        // Both should be equivalent
1069        assert_eq!(row1, row2);
1070        assert_eq!(row1.data.len(), row2.data.len());
1071
1072        // Check that all entries are initialized to zero
1073        for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1074            assert_eq!(entry1.0, entry2.0);
1075            assert_eq!(entry1.0, 0);
1076        }
1077    }
1078
1079    #[test]
1080    fn test_frame_construction() {
1081        let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1082        assert_eq!(frame.rows.len(), TEST_NROWS);
1083    }
1084
1085    #[test]
1086    fn test_frame_format() {
1087        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1088
1089        frame.format();
1090
1091        // Check that each row was formatted with correct address parameters
1092        for addr in 0..TEST_NROWS {
1093            let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1094
1095            // Check some key pixels in each row
1096            let row = &frame.rows[addr];
1097
1098            // Check last pixel has correct new address
1099            let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1100            assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1101            assert_eq!(row.data[last_pixel_idx].latch(), true);
1102
1103            // Check non-last pixels have previous address
1104            let first_pixel_idx = get_mapped_index(0);
1105            assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1106            assert_eq!(row.data[first_pixel_idx].latch(), false);
1107        }
1108    }
1109
1110    #[test]
1111    fn test_frame_set_pixel() {
1112        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1113
1114        // Test setting pixel in upper half (y < NROWS)
1115        frame.set_pixel(5, 10, true, false, true);
1116
1117        let mapped_col_10 = get_mapped_index(10);
1118        assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1119        assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1120        assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1121
1122        // Test setting pixel in lower half (y >= NROWS)
1123        frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
1124
1125        let mapped_col_15 = get_mapped_index(15);
1126        assert_eq!(frame.rows[5].data[mapped_col_15].red2(), false);
1127        assert_eq!(frame.rows[5].data[mapped_col_15].grn2(), true);
1128        assert_eq!(frame.rows[5].data[mapped_col_15].blu2(), false);
1129    }
1130
1131    #[test]
1132    fn test_frame_default() {
1133        let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1134        let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
1135
1136        // Both should be equivalent
1137        assert_eq!(frame1.rows.len(), frame2.rows.len());
1138
1139        // Check that all rows are equivalent
1140        for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
1141            assert_eq!(row1, row2);
1142
1143            // Verify all entries are zero-initialized
1144            for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1145                assert_eq!(entry1.0, entry2.0);
1146                assert_eq!(entry1.0, 0);
1147            }
1148        }
1149    }
1150
1151    #[test]
1152    fn test_dma_framebuffer_construction() {
1153        let fb = TestFrameBuffer::new();
1154        assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
1155        assert_eq!(fb._align, 0);
1156    }
1157
1158    #[test]
1159    fn test_bcm_chunk_info() {
1160        let expected_size =
1161            core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
1162        assert_eq!(TestFrameBuffer::bcm_chunk_bytes(), expected_size);
1163        assert_eq!(TestFrameBuffer::bcm_chunk_count(), 1);
1164    }
1165
1166    #[test]
1167    fn test_dma_framebuffer_erase() {
1168        let fb = TestFrameBuffer::new();
1169
1170        // After erasing, all frames should be formatted
1171        for frame in &fb.frames {
1172            for addr in 0..TEST_NROWS {
1173                let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1174
1175                // Check some key pixels in each row
1176                let row = &frame.rows[addr];
1177
1178                // Check last pixel has correct new address
1179                let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1180                assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1181                assert_eq!(row.data[last_pixel_idx].latch(), true);
1182
1183                // Check non-last pixels have previous address
1184                let first_pixel_idx = get_mapped_index(0);
1185                assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1186                assert_eq!(row.data[first_pixel_idx].latch(), false);
1187            }
1188        }
1189    }
1190
1191    #[test]
1192    fn test_dma_framebuffer_set_pixel_bounds() {
1193        let mut fb = TestFrameBuffer::new();
1194
1195        // Test negative coordinates
1196        fb.set_pixel(Point::new(-1, 5), Color::RED);
1197        fb.set_pixel(Point::new(5, -1), Color::RED);
1198
1199        // Test coordinates out of bounds (should not panic)
1200        fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
1201        fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
1202    }
1203
1204    #[test]
1205    fn test_dma_framebuffer_set_pixel_internal() {
1206        let mut fb = TestFrameBuffer::new();
1207
1208        let red_color = Color::RED;
1209        fb.set_pixel_internal(10, 5, red_color);
1210
1211        // With 3-bit depth, brightness steps are 32 (256/8)
1212        // Frames represent thresholds: 32, 64, 96, 128, 160, 192, 224
1213        // Red value 255 should activate all frames
1214        for frame in &fb.frames {
1215            // Check upper half pixel
1216            let mapped_col_10 = get_mapped_index(10);
1217            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1218            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1219            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), false);
1220        }
1221    }
1222
1223    #[test]
1224    fn test_dma_framebuffer_brightness_modulation() {
1225        let mut fb = TestFrameBuffer::new();
1226
1227        // Test with a medium brightness value
1228        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1229        let test_brightness = brightness_step * 3; // 96
1230        let color = Color::from(embedded_graphics::pixelcolor::Rgb888::new(
1231            test_brightness,
1232            0,
1233            0,
1234        ));
1235
1236        fb.set_pixel_internal(0, 0, color);
1237
1238        // Should activate frames 0, 1, 2 (thresholds 32, 64, 96)
1239        // but not frames 3, 4, 5, 6 (thresholds 128, 160, 192, 224)
1240        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1241            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1242            let should_be_active = test_brightness >= frame_threshold;
1243
1244            let mapped_col_0 = get_mapped_index(0);
1245            assert_eq!(frame.rows[0].data[mapped_col_0].red1(), should_be_active);
1246        }
1247    }
1248
1249    #[test]
1250    fn test_origin_dimensions() {
1251        let fb = TestFrameBuffer::new();
1252        let size = fb.size();
1253        assert_eq!(size.width, TEST_COLS as u32);
1254        assert_eq!(size.height, TEST_ROWS as u32);
1255
1256        // Test reference
1257        let size = (&fb).size();
1258        assert_eq!(size.width, TEST_COLS as u32);
1259        assert_eq!(size.height, TEST_ROWS as u32);
1260
1261        // Test mutable reference
1262        let mut fb = TestFrameBuffer::new();
1263        let fb_ref = &mut fb;
1264        let size = fb_ref.size();
1265        assert_eq!(size.width, TEST_COLS as u32);
1266        assert_eq!(size.height, TEST_ROWS as u32);
1267    }
1268
1269    #[test]
1270    fn test_draw_target() {
1271        let mut fb = TestFrameBuffer::new();
1272
1273        let pixels = vec![
1274            embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
1275            embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
1276            embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
1277        ];
1278
1279        let result = fb.draw_iter(pixels);
1280        assert!(result.is_ok());
1281
1282        // Test mutable reference
1283        let result = (&mut fb).draw_iter(vec![embedded_graphics::Pixel(
1284            Point::new(3, 3),
1285            Color::WHITE,
1286        )]);
1287        assert!(result.is_ok());
1288    }
1289
1290    #[test]
1291    fn test_draw_iter_pixel_verification() {
1292        let mut fb = TestFrameBuffer::new();
1293
1294        // Create test pixels with specific colors and positions
1295        let pixels = vec![
1296            // Upper half pixels (y < NROWS) - should set color0
1297            embedded_graphics::Pixel(Point::new(5, 2), Color::RED), // (5, 2) -> red
1298            embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), // (10, 5) -> green
1299            embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), // (15, 8) -> blue
1300            embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), // (20, 10) -> white
1301            // Lower half pixels (y >= NROWS) - should set color1
1302            embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), // (25, 19) -> red
1303            embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), // (30, 23) -> green
1304            embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), // (35, 28) -> blue
1305            // Edge case: black pixel (should not be visible in first frame)
1306            embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), // (40, 1) -> black
1307        ];
1308
1309        let result = fb.draw_iter(pixels);
1310        assert!(result.is_ok());
1311
1312        // Check the first frame only
1313        let first_frame = &fb.frames[0];
1314        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1315        let first_frame_threshold = brightness_step; // 32
1316
1317        // Test upper half pixels (color0)
1318        // Red pixel at (5, 2) - should be red in first frame
1319        let col_idx = get_mapped_index(5);
1320        assert_eq!(
1321            first_frame.rows[2].data[col_idx].red1(),
1322            Color::RED.r() >= first_frame_threshold
1323        );
1324        assert_eq!(
1325            first_frame.rows[2].data[col_idx].grn1(),
1326            Color::RED.g() >= first_frame_threshold
1327        );
1328        assert_eq!(
1329            first_frame.rows[2].data[col_idx].blu1(),
1330            Color::RED.b() >= first_frame_threshold
1331        );
1332
1333        // Green pixel at (10, 5) - should be green in first frame
1334        let col_idx = get_mapped_index(10);
1335        assert_eq!(
1336            first_frame.rows[5].data[col_idx].red1(),
1337            Color::GREEN.r() >= first_frame_threshold
1338        );
1339        assert_eq!(
1340            first_frame.rows[5].data[col_idx].grn1(),
1341            Color::GREEN.g() >= first_frame_threshold
1342        );
1343        assert_eq!(
1344            first_frame.rows[5].data[col_idx].blu1(),
1345            Color::GREEN.b() >= first_frame_threshold
1346        );
1347
1348        // Blue pixel at (15, 8) - should be blue in first frame
1349        let col_idx = get_mapped_index(15);
1350        assert_eq!(
1351            first_frame.rows[8].data[col_idx].red1(),
1352            Color::BLUE.r() >= first_frame_threshold
1353        );
1354        assert_eq!(
1355            first_frame.rows[8].data[col_idx].grn1(),
1356            Color::BLUE.g() >= first_frame_threshold
1357        );
1358        assert_eq!(
1359            first_frame.rows[8].data[col_idx].blu1(),
1360            Color::BLUE.b() >= first_frame_threshold
1361        );
1362
1363        // White pixel at (20, 10) - should be white in first frame
1364        let col_idx = get_mapped_index(20);
1365        assert_eq!(
1366            first_frame.rows[10].data[col_idx].red1(),
1367            Color::WHITE.r() >= first_frame_threshold
1368        );
1369        assert_eq!(
1370            first_frame.rows[10].data[col_idx].grn1(),
1371            Color::WHITE.g() >= first_frame_threshold
1372        );
1373        assert_eq!(
1374            first_frame.rows[10].data[col_idx].blu1(),
1375            Color::WHITE.b() >= first_frame_threshold
1376        );
1377
1378        // Test lower half pixels (color1)
1379        // Red pixel at (25, TEST_NROWS + 3) -> row 3, color1
1380        let col_idx = get_mapped_index(25);
1381        assert_eq!(
1382            first_frame.rows[3].data[col_idx].red2(),
1383            Color::RED.r() >= first_frame_threshold
1384        );
1385        assert_eq!(
1386            first_frame.rows[3].data[col_idx].grn2(),
1387            Color::RED.g() >= first_frame_threshold
1388        );
1389        assert_eq!(
1390            first_frame.rows[3].data[col_idx].blu2(),
1391            Color::RED.b() >= first_frame_threshold
1392        );
1393
1394        // Green pixel at (30, TEST_NROWS + 7) -> row 7, color1
1395        let col_idx = get_mapped_index(30);
1396        assert_eq!(
1397            first_frame.rows[7].data[col_idx].red2(),
1398            Color::GREEN.r() >= first_frame_threshold
1399        );
1400        assert_eq!(
1401            first_frame.rows[7].data[col_idx].grn2(),
1402            Color::GREEN.g() >= first_frame_threshold
1403        );
1404        assert_eq!(
1405            first_frame.rows[7].data[col_idx].blu2(),
1406            Color::GREEN.b() >= first_frame_threshold
1407        );
1408
1409        // Blue pixel at (35, TEST_NROWS + 12) -> row 12, color1
1410        let col_idx = get_mapped_index(35);
1411        assert_eq!(
1412            first_frame.rows[12].data[col_idx].red2(),
1413            Color::BLUE.r() >= first_frame_threshold
1414        );
1415        assert_eq!(
1416            first_frame.rows[12].data[col_idx].grn2(),
1417            Color::BLUE.g() >= first_frame_threshold
1418        );
1419        assert_eq!(
1420            first_frame.rows[12].data[col_idx].blu2(),
1421            Color::BLUE.b() >= first_frame_threshold
1422        );
1423
1424        // Test black pixel - should not be visible in any frame
1425        let col_idx = get_mapped_index(40);
1426        assert_eq!(first_frame.rows[1].data[col_idx].red1(), false);
1427        assert_eq!(first_frame.rows[1].data[col_idx].grn1(), false);
1428        assert_eq!(first_frame.rows[1].data[col_idx].blu1(), false);
1429    }
1430
1431    #[test]
1432    fn test_embedded_graphics_integration() {
1433        let mut fb = TestFrameBuffer::new();
1434
1435        // Draw a rectangle
1436        let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
1437            .into_styled(PrimitiveStyle::with_fill(Color::RED))
1438            .draw(&mut fb);
1439        assert!(result.is_ok());
1440
1441        // Draw a circle
1442        let result = Circle::new(Point::new(30, 15), 8)
1443            .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
1444            .draw(&mut fb);
1445        assert!(result.is_ok());
1446    }
1447
1448    #[test]
1449    #[cfg(feature = "skip-black-pixels")]
1450    fn test_skip_black_pixels_enabled() {
1451        let mut fb = TestFrameBuffer::new();
1452
1453        // Set a red pixel first
1454        fb.set_pixel_internal(10, 5, Color::RED);
1455
1456        // Verify it's red in the first frame
1457        let mapped_col_10 = get_mapped_index(10);
1458        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1459        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1460        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1461
1462        // Now set it to black - with skip-black-pixels enabled, this should be ignored
1463        fb.set_pixel_internal(10, 5, Color::BLACK);
1464
1465        // The pixel should still be red (black write was skipped)
1466        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1467        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1468        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1469    }
1470
1471    #[test]
1472    #[cfg(not(feature = "skip-black-pixels"))]
1473    fn test_skip_black_pixels_disabled() {
1474        let mut fb = TestFrameBuffer::new();
1475
1476        // Set a red pixel first
1477        fb.set_pixel_internal(10, 5, Color::RED);
1478
1479        // Verify it's red in the first frame
1480        let mapped_col_10 = get_mapped_index(10);
1481        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1482        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1483        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1484
1485        // Now set it to black - with skip-black-pixels disabled, this should overwrite
1486        fb.set_pixel_internal(10, 5, Color::BLACK);
1487
1488        // The pixel should now be black (all bits false)
1489        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1490        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1491        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1492    }
1493
1494    #[test]
1495    fn test_bcm_frame_overwrite() {
1496        let mut fb = TestFrameBuffer::new();
1497
1498        // First write a white pixel (255, 255, 255)
1499        fb.set_pixel_internal(10, 5, Color::WHITE);
1500
1501        let mapped_col_10 = get_mapped_index(10);
1502
1503        // Verify white pixel is lit in all frames (255 >= all thresholds)
1504        for frame in fb.frames.iter() {
1505            // White (255) should be active in all frames since it's >= all thresholds
1506            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1507            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), true);
1508            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1509        }
1510
1511        // Now overwrite with 50% white (128, 128, 128)
1512        let half_white = Color::from(embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128));
1513        fb.set_pixel_internal(10, 5, half_white);
1514
1515        // Verify only the correct frames are lit for 50% white
1516        // With 3-bit depth: thresholds are 32, 64, 96, 128, 160, 192, 224
1517        // 128 should activate frames 0, 1, 2, 3 (thresholds 32, 64, 96, 128)
1518        // but not frames 4, 5, 6 (thresholds 160, 192, 224)
1519        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1520        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1521            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1522            let should_be_active = 128 >= frame_threshold;
1523
1524            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), should_be_active);
1525            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), should_be_active);
1526            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), should_be_active);
1527        }
1528
1529        // Specifically verify the expected pattern for 3-bit depth
1530        // Frames 0-3 should be active (thresholds 32, 64, 96, 128)
1531        for frame_idx in 0..4 {
1532            assert_eq!(
1533                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1534                true
1535            );
1536        }
1537        // Frames 4-6 should be inactive (thresholds 160, 192, 224)
1538        for frame_idx in 4..TEST_FRAME_COUNT {
1539            assert_eq!(
1540                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1541                false
1542            );
1543        }
1544    }
1545
1546    #[test]
1547    fn test_read_buffer_implementation() {
1548        // Test owned implementation - explicitly move the framebuffer to ensure we're testing the owned impl
1549        let fb = TestFrameBuffer::new();
1550        let expected_size = core::mem::size_of_val(&fb.frames);
1551
1552        // Test owned ReadBuffer implementation by calling ReadBuffer::read_buffer explicitly
1553        unsafe {
1554            let (ptr, len) = <TestFrameBuffer as ReadBuffer>::read_buffer(&fb);
1555            assert!(!ptr.is_null());
1556            assert_eq!(len, expected_size);
1557        }
1558
1559        // Test direct method call on owned value
1560        unsafe {
1561            let (ptr, len) = fb.read_buffer();
1562            assert!(!ptr.is_null());
1563            assert_eq!(len, expected_size);
1564        }
1565
1566        // Test reference implementation
1567        let fb = TestFrameBuffer::new();
1568        let fb_ref = &fb;
1569        unsafe {
1570            let (ptr, len) = fb_ref.read_buffer();
1571            assert!(!ptr.is_null());
1572            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1573        }
1574
1575        // Test mutable reference implementation
1576        let mut fb = TestFrameBuffer::new();
1577        let fb_ref = &mut fb;
1578        unsafe {
1579            let (ptr, len) = fb_ref.read_buffer();
1580            assert!(!ptr.is_null());
1581            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1582        }
1583    }
1584
1585    #[test]
1586    fn test_read_buffer_owned_implementation() {
1587        // This test specifically ensures the owned ReadBuffer implementation is tested
1588        // by consuming the framebuffer and testing the pointer validity
1589        fn test_owned_read_buffer(fb: TestFrameBuffer) -> (bool, usize) {
1590            unsafe {
1591                let (ptr, len) = fb.read_buffer();
1592                (!ptr.is_null(), len)
1593            }
1594        }
1595
1596        let fb = TestFrameBuffer::new();
1597        let expected_len = core::mem::size_of_val(&fb.frames);
1598
1599        let (ptr_valid, actual_len) = test_owned_read_buffer(fb);
1600        assert!(ptr_valid);
1601        assert_eq!(actual_len, expected_len);
1602    }
1603
1604    #[test]
1605    fn test_framebuffer_trait() {
1606        let fb = TestFrameBuffer::new();
1607        assert_eq!(fb.get_word_size(), WordSize::Sixteen);
1608
1609        let fb_ref = &fb;
1610        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1611
1612        let mut fb = TestFrameBuffer::new();
1613        let fb_ref = &mut fb;
1614        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1615    }
1616
1617    #[test]
1618    fn test_debug_formatting() {
1619        let fb = TestFrameBuffer::new();
1620        let debug_string = format!("{:?}", fb);
1621        assert!(debug_string.contains("DmaFrameBuffer"));
1622        assert!(debug_string.contains("frame_count"));
1623        assert!(debug_string.contains("frame_size"));
1624        assert!(debug_string.contains("brightness_step"));
1625    }
1626
1627    #[test]
1628    fn test_default_implementation() {
1629        let fb1 = TestFrameBuffer::new();
1630        let fb2 = TestFrameBuffer::default();
1631
1632        // Both should be equivalent in size, but may differ in content since new() calls format()
1633        assert_eq!(fb1.frames.len(), fb2.frames.len());
1634        assert_eq!(fb1._align, fb2._align);
1635    }
1636
1637    #[test]
1638    fn test_memory_alignment() {
1639        let fb = TestFrameBuffer::new();
1640        let ptr = &fb as *const _ as usize;
1641
1642        // Should be aligned to 64-bit boundary due to the _align field
1643        assert_eq!(ptr % 8, 0);
1644    }
1645
1646    #[test]
1647    fn test_color_values() {
1648        let mut fb = TestFrameBuffer::new();
1649
1650        // Test different color values
1651        let colors = [
1652            (Color::RED, (255, 0, 0)),
1653            (Color::GREEN, (0, 255, 0)),
1654            (Color::BLUE, (0, 0, 255)),
1655            (Color::WHITE, (255, 255, 255)),
1656            (Color::BLACK, (0, 0, 0)),
1657        ];
1658
1659        for (i, (color, (r, g, b))) in colors.iter().enumerate() {
1660            fb.set_pixel(Point::new(i as i32, 0), *color);
1661            assert_eq!(color.r(), *r);
1662            assert_eq!(color.g(), *g);
1663            assert_eq!(color.b(), *b);
1664        }
1665    }
1666
1667    #[test]
1668    fn test_blanking_delay() {
1669        let mut row: Row<TEST_COLS> = Row::new();
1670        let test_addr = 5;
1671        let prev_addr = 4;
1672
1673        row.format(test_addr, prev_addr);
1674
1675        // Test that the blanking delay is respected
1676        let blanking_pixel_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 1);
1677        assert_eq!(row.data[blanking_pixel_idx].output_enable(), false);
1678
1679        // Test that pixels before blanking delay have output enabled (if after pixel 1)
1680        let before_blanking_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 2);
1681        assert_eq!(row.data[before_blanking_idx].output_enable(), true);
1682    }
1683
1684    #[test]
1685    fn test_esp32_mapping() {
1686        // Test the ESP32-specific index mapping
1687        #[cfg(feature = "esp32-ordering")]
1688        {
1689            assert_eq!(map_index(0), 1);
1690            assert_eq!(map_index(1), 0);
1691            assert_eq!(map_index(2), 3);
1692            assert_eq!(map_index(3), 2);
1693            assert_eq!(map_index(4), 5);
1694            assert_eq!(map_index(5), 4);
1695        }
1696        #[cfg(not(feature = "esp32-ordering"))]
1697        {
1698            assert_eq!(map_index(0), 0);
1699            assert_eq!(map_index(1), 1);
1700            assert_eq!(map_index(2), 2);
1701            assert_eq!(map_index(3), 3);
1702        }
1703    }
1704
1705    #[test]
1706    fn test_bits_assertion() {
1707        // Test that BITS <= 8 assertion is enforced at compile time
1708        // This test mainly documents the constraint
1709        assert!(TEST_BITS <= 8);
1710    }
1711
1712    #[test]
1713    fn test_fast_clear_method() {
1714        let mut fb = TestFrameBuffer::new();
1715
1716        // Set some pixels
1717        fb.set_pixel_internal(10, 5, Color::RED);
1718        fb.set_pixel_internal(20, 10, Color::GREEN);
1719
1720        // Verify pixels are set
1721        let mapped_col_10 = get_mapped_index(10);
1722        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1723
1724        // Clear using fast method
1725        fb.erase();
1726
1727        // Verify pixels are cleared but timing signals remain
1728        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1729        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1730        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1731
1732        // Verify timing signals are still present (check last pixel has latch)
1733        let last_col = get_mapped_index(TEST_COLS - 1);
1734        assert_eq!(fb.frames[0].rows[5].data[last_col].latch(), true);
1735    }
1736
1737    // Remove the old `test_draw_char_bottom_right` and replace with a helper + combined test.
1738
1739    // Constants for FONT_6X10 glyph size
1740    const CHAR_W: i32 = 6;
1741    const CHAR_H: i32 = 10;
1742
1743    /// Draws glyph 'A' at `origin` and validates framebuffer pixels against a reference.
1744    fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
1745        use embedded_graphics::mock_display::MockDisplay;
1746        use embedded_graphics::mono_font::ascii::FONT_6X10;
1747        use embedded_graphics::mono_font::MonoTextStyle;
1748        use embedded_graphics::text::{Baseline, Text};
1749
1750        // Draw the glyph
1751        let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
1752        Text::with_baseline("A", origin, style, Baseline::Top)
1753            .draw(fb)
1754            .unwrap();
1755
1756        // Reference glyph at (0,0)
1757        let mut reference: MockDisplay<Color> = MockDisplay::new();
1758        Text::with_baseline("A", Point::zero(), style, Baseline::Top)
1759            .draw(&mut reference)
1760            .unwrap();
1761
1762        for dy in 0..CHAR_H {
1763            for dx in 0..CHAR_W {
1764                let expected_on = reference
1765                    .get_pixel(Point::new(dx, dy))
1766                    .unwrap_or(Color::BLACK)
1767                    != Color::BLACK;
1768
1769                let gx = (origin.x + dx) as usize;
1770                let gy = (origin.y + dy) as usize;
1771
1772                // we have computed the origin to be within the panel, so we don't need to check for bounds
1773                // if gx >= TEST_COLS || gy >= TEST_ROWS {
1774                //     continue;
1775                // }
1776
1777                // Fetch Entry from frame 0
1778                let frame0 = &fb.frames[0];
1779                let e = if gy < TEST_NROWS {
1780                    &frame0.rows[gy].data[get_mapped_index(gx)]
1781                } else {
1782                    &frame0.rows[gy - TEST_NROWS].data[get_mapped_index(gx)]
1783                };
1784
1785                let (r, g, b) = if gy >= TEST_NROWS {
1786                    (e.red2(), e.grn2(), e.blu2())
1787                } else {
1788                    (e.red1(), e.grn1(), e.blu1())
1789                };
1790
1791                if expected_on {
1792                    assert!(r && g && b,);
1793                } else {
1794                    assert!(!r && !g && !b);
1795                }
1796            }
1797        }
1798    }
1799
1800    #[test]
1801    fn test_draw_char_corners() {
1802        let upper_left = Point::new(0, 0);
1803        let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
1804
1805        let mut fb = TestFrameBuffer::new();
1806
1807        verify_glyph_at(&mut fb, upper_left);
1808        verify_glyph_at(&mut fb, lower_right);
1809    }
1810
1811    #[test]
1812    fn test_framebuffer_operations_trait_erase() {
1813        let mut fb = TestFrameBuffer::new();
1814
1815        // Set pixels so erase has work to do
1816        fb.set_pixel_internal(10, 5, Color::RED);
1817        fb.set_pixel_internal(20, 10, Color::GREEN);
1818
1819        // Explicitly call trait method to hit the FrameBufferOperations impl
1820        <TestFrameBuffer as FrameBufferOperations>::erase(&mut fb);
1821
1822        // Verify colors cleared on frame 0
1823        let mc10 = get_mapped_index(10);
1824        let mc20 = get_mapped_index(20);
1825        assert_eq!(fb.frames[0].rows[5].data[mc10].red1(), false);
1826        assert_eq!(fb.frames[0].rows[10].data[mc20].grn1(), false);
1827
1828        // Timing signals preserved: last pixel should have latch
1829        let last_col = get_mapped_index(TEST_COLS - 1);
1830        assert!(fb.frames[0].rows[0].data[last_col].latch());
1831    }
1832
1833    #[test]
1834    fn test_framebuffer_operations_trait_set_pixel() {
1835        let mut fb = TestFrameBuffer::new();
1836
1837        // Explicitly call trait method to hit the FrameBufferOperations impl
1838        <TestFrameBuffer as FrameBufferOperations>::set_pixel(
1839            &mut fb,
1840            Point::new(8, 3),
1841            Color::BLUE,
1842        );
1843
1844        let idx = get_mapped_index(8);
1845        assert_eq!(fb.frames[0].rows[3].data[idx].blu1(), true);
1846        assert_eq!(fb.frames[0].rows[3].data[idx].red1(), false);
1847        assert_eq!(fb.frames[0].rows[3].data[idx].grn1(), false);
1848    }
1849}