use core::convert::Infallible;
use crate::FrameBufferOperations;
use bitfield::bitfield;
#[cfg(not(feature = "esp-hal-dma"))]
use embedded_dma::ReadBuffer;
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::Point;
#[cfg(feature = "esp-hal-dma")]
use esp_hal::dma::ReadBuffer;
use super::Color;
use super::FrameBuffer;
use super::WordSize;
const BLANKING_DELAY: usize = 1;
#[inline]
const fn make_data_template<const COLS: usize>(addr: u8, prev_addr: u8) -> [Entry; COLS] {
let mut data = [Entry::new(); COLS];
let mut i = 0;
while i < COLS {
let mut entry = Entry::new();
entry.0 = prev_addr as u16;
if i == 1 {
entry.0 |= 0b1_0000_0000; } else if i == COLS - BLANKING_DELAY - 1 {
} else if i == COLS - 1 {
entry.0 |= 0b0010_0000; entry.0 = (entry.0 & !0b0001_1111) | (addr as u16); } else if i > 1 && i < COLS - BLANKING_DELAY - 1 {
entry.0 |= 0b1_0000_0000; }
data[map_index(i)] = entry;
i += 1;
}
data
}
bitfield! {
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
struct Entry(u16);
dummy2, set_dummy2: 15;
blu2, set_blu2: 14;
grn2, set_grn2: 13;
red2, set_red2: 12;
blu1, set_blu1: 11;
grn1, set_grn1: 10;
red1, set_red1: 9;
output_enable, set_output_enable: 8;
dummy1, set_dummy1: 7;
dummy0, set_dummy0: 6;
latch, set_latch: 5;
addr, set_addr: 4, 0;
}
impl core::fmt::Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Entry")
.field(&format_args!("{:#x}", self.0))
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Entry {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "Entry({=u16:#x})", self.0);
}
}
impl Entry {
const fn new() -> Self {
Self(0)
}
const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; const COLOR1_MASK: u16 = 0b0111_0000_0000_0000;
#[inline]
fn set_color0_bits(&mut self, bits: u8) {
let bits16 = u16::from(bits) << 9;
self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
}
#[inline]
fn set_color1_bits(&mut self, bits: u8) {
let bits16 = u16::from(bits) << 12;
self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct Row<const COLS: usize> {
data: [Entry; COLS],
}
const fn map_index(i: usize) -> usize {
#[cfg(feature = "esp32-ordering")]
{
i ^ 1
}
#[cfg(not(feature = "esp32-ordering"))]
{
i
}
}
impl<const COLS: usize> Default for Row<COLS> {
fn default() -> Self {
Self::new()
}
}
impl<const COLS: usize> Row<COLS> {
pub const fn new() -> Self {
Self {
data: [Entry::new(); COLS],
}
}
pub fn format(&mut self, addr: u8, prev_addr: u8) {
let template = make_data_template::<COLS>(addr, prev_addr);
self.data.copy_from_slice(&template);
}
#[inline]
pub fn clear_colors(&mut self) {
const COLOR_CLEAR_MASK: u16 = !0b0111_1110_0000_0000;
for entry in &mut self.data {
entry.0 &= COLOR_CLEAR_MASK;
}
}
#[inline]
pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
let col = map_index(col);
self.data[col].set_color0_bits(bits);
}
#[inline]
pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
let col = map_index(col);
self.data[col].set_color1_bits(bits);
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
rows: [Row<COLS>; NROWS],
}
impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
pub const fn new() -> Self {
Self {
rows: [Row::new(); NROWS],
}
}
pub fn format(&mut self) {
for (addr, row) in self.rows.iter_mut().enumerate() {
let prev_addr = if addr == 0 {
NROWS as u8 - 1
} else {
addr as u8 - 1
};
row.format(addr as u8, prev_addr);
}
}
#[inline]
pub fn clear_colors(&mut self) {
for row in &mut self.rows {
row.clear_colors();
}
}
#[inline]
pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
let row = &mut self.rows[if y < NROWS { y } else { y - NROWS }];
if y < NROWS {
row.set_color0(x, red, green, blue);
} else {
row.set_color1(x, red, green, blue);
}
}
}
impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
for Frame<ROWS, COLS, NROWS>
{
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct DmaFrameBuffer<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> {
_align: u64,
frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn default() -> Self {
Self::new()
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[must_use]
pub fn new() -> Self {
debug_assert!(BITS <= 8);
let mut instance = Self {
_align: 0,
frames: [Frame::new(); FRAME_COUNT],
};
instance.format();
instance
}
#[cfg(feature = "esp-hal-dma")]
#[must_use]
pub const fn dma_buffer_size_bytes() -> usize {
core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
}
#[inline]
pub fn format(&mut self) {
for frame in &mut self.frames {
frame.format();
}
}
#[inline]
pub fn erase(&mut self) {
for frame in &mut self.frames {
frame.clear_colors();
}
}
pub fn set_pixel(&mut self, p: Point, color: Color) {
if p.x < 0 || p.y < 0 {
return;
}
self.set_pixel_internal(p.x as usize, p.y as usize, color);
}
#[inline]
fn frames_on(v: u8) -> usize {
(v as usize) >> (8 - BITS)
}
#[inline]
fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
if x >= COLS || y >= ROWS {
return;
}
#[cfg(feature = "skip-black-pixels")]
if color == Color::BLACK {
return;
}
let red_frames = Self::frames_on(color.r());
let green_frames = Self::frames_on(color.g());
let blue_frames = Self::frames_on(color.b());
for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
frame.set_pixel(
y,
x,
frame_idx < red_frames,
frame_idx < green_frames,
frame_idx < blue_frames,
);
}
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> FrameBufferOperations<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[inline]
fn erase(&mut self) {
DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
}
#[inline]
fn set_pixel(&mut self, p: Point, color: Color) {
DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::prelude::OriginDimensions
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn size(&self) -> embedded_graphics::prelude::Size {
embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::prelude::OriginDimensions
for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn size(&self) -> embedded_graphics::prelude::Size {
embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::draw_target::DrawTarget
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
type Color = Color;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
{
for pixel in pixels {
self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
}
Ok(())
}
}
unsafe impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> ReadBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[cfg(not(feature = "esp-hal-dma"))]
type Word = u8;
unsafe fn read_buffer(&self) -> (*const u8, usize) {
let ptr = (&raw const self.frames).cast::<u8>();
let len = core::mem::size_of_val(&self.frames);
(ptr, len)
}
}
unsafe impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> ReadBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[cfg(not(feature = "esp-hal-dma"))]
type Word = u8;
unsafe fn read_buffer(&self) -> (*const u8, usize) {
let ptr = (&raw const self.frames).cast::<u8>();
let len = core::mem::size_of_val(&self.frames);
(ptr, len)
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let brightness_step = 1 << (8 - BITS);
f.debug_struct("DmaFrameBuffer")
.field("size", &core::mem::size_of_val(&self.frames))
.field("frame_count", &self.frames.len())
.field("frame_size", &core::mem::size_of_val(&self.frames[0]))
.field("brightness_step", &&brightness_step)
.finish_non_exhaustive()
}
}
#[cfg(feature = "defmt")]
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn format(&self, f: defmt::Formatter) {
let brightness_step = 1 << (8 - BITS);
defmt::write!(
f,
"DmaFrameBuffer<{}, {}, {}, {}, {}>",
ROWS,
COLS,
NROWS,
BITS,
FRAME_COUNT
);
defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
defmt::write!(
f,
" frame_size: {}",
core::mem::size_of_val(&self.frames[0])
);
defmt::write!(f, " brightness_step: {}", brightness_step);
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn get_word_size(&self) -> WordSize {
WordSize::Sixteen
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn get_word_size(&self) -> WordSize {
WordSize::Sixteen
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::format;
use std::vec;
use super::*;
use crate::{FrameBuffer, WordSize};
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
const TEST_ROWS: usize = 32;
const TEST_COLS: usize = 64;
const TEST_NROWS: usize = TEST_ROWS / 2;
const TEST_BITS: u8 = 3;
const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1;
type TestFrameBuffer =
DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
fn get_mapped_index(index: usize) -> usize {
map_index(index)
}
#[test]
fn test_entry_construction() {
let entry = Entry::new();
assert_eq!(entry.0, 0);
assert_eq!(entry.dummy2(), false);
assert_eq!(entry.blu2(), false);
assert_eq!(entry.grn2(), false);
assert_eq!(entry.red2(), false);
assert_eq!(entry.blu1(), false);
assert_eq!(entry.grn1(), false);
assert_eq!(entry.red1(), false);
assert_eq!(entry.output_enable(), false);
assert_eq!(entry.dummy1(), false);
assert_eq!(entry.dummy0(), false);
assert_eq!(entry.latch(), false);
assert_eq!(entry.addr(), 0);
}
#[test]
fn test_entry_setters() {
let mut entry = Entry::new();
entry.set_dummy2(true);
assert_eq!(entry.dummy2(), true);
assert_eq!(entry.0 & 0b1000000000000000, 0b1000000000000000);
entry.set_blu2(true);
assert_eq!(entry.blu2(), true);
assert_eq!(entry.0 & 0b0100000000000000, 0b0100000000000000);
entry.set_grn2(true);
assert_eq!(entry.grn2(), true);
assert_eq!(entry.0 & 0b0010000000000000, 0b0010000000000000);
entry.set_red2(true);
assert_eq!(entry.red2(), true);
assert_eq!(entry.0 & 0b0001000000000000, 0b0001000000000000);
entry.set_blu1(true);
assert_eq!(entry.blu1(), true);
assert_eq!(entry.0 & 0b0000100000000000, 0b0000100000000000);
entry.set_grn1(true);
assert_eq!(entry.grn1(), true);
assert_eq!(entry.0 & 0b0000010000000000, 0b0000010000000000);
entry.set_red1(true);
assert_eq!(entry.red1(), true);
assert_eq!(entry.0 & 0b0000001000000000, 0b0000001000000000);
entry.set_output_enable(true);
assert_eq!(entry.output_enable(), true);
assert_eq!(entry.0 & 0b0000000100000000, 0b0000000100000000);
entry.set_dummy1(true);
assert_eq!(entry.dummy1(), true);
assert_eq!(entry.0 & 0b0000000010000000, 0b0000000010000000);
entry.set_dummy0(true);
assert_eq!(entry.dummy0(), true);
assert_eq!(entry.0 & 0b0000000001000000, 0b0000000001000000);
entry.set_latch(true);
assert_eq!(entry.latch(), true);
assert_eq!(entry.0 & 0b0000000000100000, 0b0000000000100000);
entry.set_addr(0b11111);
assert_eq!(entry.addr(), 0b11111);
assert_eq!(entry.0 & 0b0000000000011111, 0b0000000000011111);
}
#[test]
fn test_entry_bit_isolation() {
let mut entry = Entry::new();
entry.set_addr(0b11111);
entry.set_latch(true);
assert_eq!(entry.addr(), 0b11111);
assert_eq!(entry.latch(), true);
assert_eq!(entry.output_enable(), false);
assert_eq!(entry.red1(), false);
entry.set_red1(true);
entry.set_grn2(true);
assert_eq!(entry.addr(), 0b11111);
assert_eq!(entry.latch(), true);
assert_eq!(entry.red1(), true);
assert_eq!(entry.grn2(), true);
assert_eq!(entry.blu1(), false);
assert_eq!(entry.red2(), false);
}
#[test]
fn test_entry_set_color0() {
let mut entry = Entry::new();
let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); entry.set_color0_bits(bits);
assert_eq!(entry.red1(), true);
assert_eq!(entry.grn1(), false);
assert_eq!(entry.blu1(), true);
assert_eq!(entry.0 & 0b0000101000000000, 0b0000101000000000); }
#[test]
fn test_entry_set_color1() {
let mut entry = Entry::new();
let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); entry.set_color1_bits(bits);
assert_eq!(entry.red2(), false);
assert_eq!(entry.grn2(), true);
assert_eq!(entry.blu2(), true);
assert_eq!(entry.0 & 0b0110000000000000, 0b0110000000000000); }
#[test]
fn test_entry_debug_formatting() {
let entry = Entry(0x1234);
let debug_str = format!("{:?}", entry);
assert_eq!(debug_str, "Entry(0x1234)");
let entry = Entry(0xabcd);
let debug_str = format!("{:?}", entry);
assert_eq!(debug_str, "Entry(0xabcd)");
}
#[test]
fn test_row_construction() {
let row: Row<TEST_COLS> = Row::new();
assert_eq!(row.data.len(), TEST_COLS);
for entry in &row.data {
assert_eq!(entry.0, 0);
}
}
#[test]
fn test_row_format() {
let mut row: Row<TEST_COLS> = Row::new();
let test_addr = 5;
let prev_addr = 4;
row.format(test_addr, prev_addr);
for (physical_i, entry) in row.data.iter().enumerate() {
let logical_i = get_mapped_index(physical_i);
match logical_i {
i if i == TEST_COLS - BLANKING_DELAY - 1 => {
assert_eq!(entry.output_enable(), false);
assert_eq!(entry.addr(), prev_addr as u16);
assert_eq!(entry.latch(), false);
}
i if i == TEST_COLS - 1 => {
assert_eq!(entry.latch(), true);
assert_eq!(entry.addr(), test_addr as u16);
assert_eq!(entry.output_enable(), false);
}
1 => {
assert_eq!(entry.output_enable(), true);
assert_eq!(entry.addr(), prev_addr as u16);
assert_eq!(entry.latch(), false);
}
_ => {
assert_eq!(entry.addr(), prev_addr as u16);
assert_eq!(entry.latch(), false);
if logical_i > 1 && logical_i < TEST_COLS - BLANKING_DELAY - 1 {
assert_eq!(entry.output_enable(), true);
}
}
}
}
}
#[test]
fn test_row_set_color0() {
let mut row: Row<TEST_COLS> = Row::new();
row.set_color0(0, true, false, true);
let mapped_col_0 = get_mapped_index(0);
assert_eq!(row.data[mapped_col_0].red1(), true);
assert_eq!(row.data[mapped_col_0].grn1(), false);
assert_eq!(row.data[mapped_col_0].blu1(), true);
row.set_color0(1, false, true, false);
let mapped_col_1 = get_mapped_index(1);
assert_eq!(row.data[mapped_col_1].red1(), false);
assert_eq!(row.data[mapped_col_1].grn1(), true);
assert_eq!(row.data[mapped_col_1].blu1(), false);
}
#[test]
fn test_row_set_color1() {
let mut row: Row<TEST_COLS> = Row::new();
row.set_color1(0, true, true, false);
let mapped_col_0 = get_mapped_index(0);
assert_eq!(row.data[mapped_col_0].red2(), true);
assert_eq!(row.data[mapped_col_0].grn2(), true);
assert_eq!(row.data[mapped_col_0].blu2(), false);
}
#[test]
fn test_row_default() {
let row1: Row<TEST_COLS> = Row::new();
let row2: Row<TEST_COLS> = Row::default();
assert_eq!(row1, row2);
assert_eq!(row1.data.len(), row2.data.len());
for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
assert_eq!(entry1.0, entry2.0);
assert_eq!(entry1.0, 0);
}
}
#[test]
fn test_frame_construction() {
let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
assert_eq!(frame.rows.len(), TEST_NROWS);
}
#[test]
fn test_frame_format() {
let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
frame.format();
for addr in 0..TEST_NROWS {
let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
let row = &frame.rows[addr];
let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
assert_eq!(row.data[last_pixel_idx].latch(), true);
let first_pixel_idx = get_mapped_index(0);
assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
assert_eq!(row.data[first_pixel_idx].latch(), false);
}
}
#[test]
fn test_frame_set_pixel() {
let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
frame.set_pixel(5, 10, true, false, true);
let mapped_col_10 = get_mapped_index(10);
assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
let mapped_col_15 = get_mapped_index(15);
assert_eq!(frame.rows[5].data[mapped_col_15].red2(), false);
assert_eq!(frame.rows[5].data[mapped_col_15].grn2(), true);
assert_eq!(frame.rows[5].data[mapped_col_15].blu2(), false);
}
#[test]
fn test_frame_default() {
let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
assert_eq!(frame1.rows.len(), frame2.rows.len());
for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
assert_eq!(row1, row2);
for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
assert_eq!(entry1.0, entry2.0);
assert_eq!(entry1.0, 0);
}
}
}
#[test]
fn test_dma_framebuffer_construction() {
let fb = TestFrameBuffer::new();
assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
assert_eq!(fb._align, 0);
}
#[test]
#[cfg(feature = "esp-hal-dma")]
fn test_dma_framebuffer_dma_buffer_size() {
let expected_size =
core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
assert_eq!(TestFrameBuffer::dma_buffer_size_bytes(), expected_size);
}
#[test]
fn test_dma_framebuffer_erase() {
let fb = TestFrameBuffer::new();
for frame in &fb.frames {
for addr in 0..TEST_NROWS {
let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
let row = &frame.rows[addr];
let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
assert_eq!(row.data[last_pixel_idx].latch(), true);
let first_pixel_idx = get_mapped_index(0);
assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
assert_eq!(row.data[first_pixel_idx].latch(), false);
}
}
}
#[test]
fn test_dma_framebuffer_set_pixel_bounds() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel(Point::new(-1, 5), Color::RED);
fb.set_pixel(Point::new(5, -1), Color::RED);
fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
}
#[test]
fn test_dma_framebuffer_set_pixel_internal() {
let mut fb = TestFrameBuffer::new();
let red_color = Color::RED;
fb.set_pixel_internal(10, 5, red_color);
for frame in &fb.frames {
let mapped_col_10 = get_mapped_index(10);
assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), false);
}
}
#[test]
fn test_dma_framebuffer_brightness_modulation() {
let mut fb = TestFrameBuffer::new();
let brightness_step = 1 << (8 - TEST_BITS); let test_brightness = brightness_step * 3; let color = Color::from(embedded_graphics::pixelcolor::Rgb888::new(
test_brightness,
0,
0,
));
fb.set_pixel_internal(0, 0, color);
for (frame_idx, frame) in fb.frames.iter().enumerate() {
let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
let should_be_active = test_brightness >= frame_threshold;
let mapped_col_0 = get_mapped_index(0);
assert_eq!(frame.rows[0].data[mapped_col_0].red1(), should_be_active);
}
}
#[test]
fn test_origin_dimensions() {
let fb = TestFrameBuffer::new();
let size = fb.size();
assert_eq!(size.width, TEST_COLS as u32);
assert_eq!(size.height, TEST_ROWS as u32);
let size = (&fb).size();
assert_eq!(size.width, TEST_COLS as u32);
assert_eq!(size.height, TEST_ROWS as u32);
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
let size = fb_ref.size();
assert_eq!(size.width, TEST_COLS as u32);
assert_eq!(size.height, TEST_ROWS as u32);
}
#[test]
fn test_draw_target() {
let mut fb = TestFrameBuffer::new();
let pixels = vec![
embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
];
let result = fb.draw_iter(pixels);
assert!(result.is_ok());
let result = (&mut fb).draw_iter(vec![embedded_graphics::Pixel(
Point::new(3, 3),
Color::WHITE,
)]);
assert!(result.is_ok());
}
#[test]
fn test_draw_iter_pixel_verification() {
let mut fb = TestFrameBuffer::new();
let pixels = vec![
embedded_graphics::Pixel(Point::new(5, 2), Color::RED), embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), ];
let result = fb.draw_iter(pixels);
assert!(result.is_ok());
let first_frame = &fb.frames[0];
let brightness_step = 1 << (8 - TEST_BITS); let first_frame_threshold = brightness_step;
let col_idx = get_mapped_index(5);
assert_eq!(
first_frame.rows[2].data[col_idx].red1(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[2].data[col_idx].grn1(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[2].data[col_idx].blu1(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(10);
assert_eq!(
first_frame.rows[5].data[col_idx].red1(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[5].data[col_idx].grn1(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[5].data[col_idx].blu1(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(15);
assert_eq!(
first_frame.rows[8].data[col_idx].red1(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[8].data[col_idx].grn1(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[8].data[col_idx].blu1(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(20);
assert_eq!(
first_frame.rows[10].data[col_idx].red1(),
Color::WHITE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[10].data[col_idx].grn1(),
Color::WHITE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[10].data[col_idx].blu1(),
Color::WHITE.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(25);
assert_eq!(
first_frame.rows[3].data[col_idx].red2(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[3].data[col_idx].grn2(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[3].data[col_idx].blu2(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(30);
assert_eq!(
first_frame.rows[7].data[col_idx].red2(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[7].data[col_idx].grn2(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[7].data[col_idx].blu2(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(35);
assert_eq!(
first_frame.rows[12].data[col_idx].red2(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[12].data[col_idx].grn2(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[12].data[col_idx].blu2(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = get_mapped_index(40);
assert_eq!(first_frame.rows[1].data[col_idx].red1(), false);
assert_eq!(first_frame.rows[1].data[col_idx].grn1(), false);
assert_eq!(first_frame.rows[1].data[col_idx].blu1(), false);
}
#[test]
fn test_embedded_graphics_integration() {
let mut fb = TestFrameBuffer::new();
let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
.into_styled(PrimitiveStyle::with_fill(Color::RED))
.draw(&mut fb);
assert!(result.is_ok());
let result = Circle::new(Point::new(30, 15), 8)
.into_styled(PrimitiveStyle::with_fill(Color::BLUE))
.draw(&mut fb);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "skip-black-pixels")]
fn test_skip_black_pixels_enabled() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
let mapped_col_10 = get_mapped_index(10);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
}
#[test]
#[cfg(not(feature = "skip-black-pixels"))]
fn test_skip_black_pixels_disabled() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
let mapped_col_10 = get_mapped_index(10);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
}
#[test]
fn test_bcm_frame_overwrite() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::WHITE);
let mapped_col_10 = get_mapped_index(10);
for frame in fb.frames.iter() {
assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
}
let half_white = Color::from(embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128));
fb.set_pixel_internal(10, 5, half_white);
let brightness_step = 1 << (8 - TEST_BITS); for (frame_idx, frame) in fb.frames.iter().enumerate() {
let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
let should_be_active = 128 >= frame_threshold;
assert_eq!(frame.rows[5].data[mapped_col_10].red1(), should_be_active);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), should_be_active);
assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), should_be_active);
}
for frame_idx in 0..4 {
assert_eq!(
fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
true
);
}
for frame_idx in 4..TEST_FRAME_COUNT {
assert_eq!(
fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
false
);
}
}
#[test]
fn test_read_buffer_implementation() {
let fb = TestFrameBuffer::new();
let expected_size = core::mem::size_of_val(&fb.frames);
unsafe {
let (ptr, len) = <TestFrameBuffer as ReadBuffer>::read_buffer(&fb);
assert!(!ptr.is_null());
assert_eq!(len, expected_size);
}
unsafe {
let (ptr, len) = fb.read_buffer();
assert!(!ptr.is_null());
assert_eq!(len, expected_size);
}
let fb = TestFrameBuffer::new();
let fb_ref = &fb;
unsafe {
let (ptr, len) = fb_ref.read_buffer();
assert!(!ptr.is_null());
assert_eq!(len, core::mem::size_of_val(&fb.frames));
}
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
unsafe {
let (ptr, len) = fb_ref.read_buffer();
assert!(!ptr.is_null());
assert_eq!(len, core::mem::size_of_val(&fb.frames));
}
}
#[test]
fn test_read_buffer_owned_implementation() {
fn test_owned_read_buffer(fb: TestFrameBuffer) -> (bool, usize) {
unsafe {
let (ptr, len) = fb.read_buffer();
(!ptr.is_null(), len)
}
}
let fb = TestFrameBuffer::new();
let expected_len = core::mem::size_of_val(&fb.frames);
let (ptr_valid, actual_len) = test_owned_read_buffer(fb);
assert!(ptr_valid);
assert_eq!(actual_len, expected_len);
}
#[test]
fn test_framebuffer_trait() {
let fb = TestFrameBuffer::new();
assert_eq!(fb.get_word_size(), WordSize::Sixteen);
let fb_ref = &fb;
assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
}
#[test]
fn test_debug_formatting() {
let fb = TestFrameBuffer::new();
let debug_string = format!("{:?}", fb);
assert!(debug_string.contains("DmaFrameBuffer"));
assert!(debug_string.contains("frame_count"));
assert!(debug_string.contains("frame_size"));
assert!(debug_string.contains("brightness_step"));
}
#[test]
fn test_default_implementation() {
let fb1 = TestFrameBuffer::new();
let fb2 = TestFrameBuffer::default();
assert_eq!(fb1.frames.len(), fb2.frames.len());
assert_eq!(fb1._align, fb2._align);
}
#[test]
fn test_memory_alignment() {
let fb = TestFrameBuffer::new();
let ptr = &fb as *const _ as usize;
assert_eq!(ptr % 8, 0);
}
#[test]
fn test_color_values() {
let mut fb = TestFrameBuffer::new();
let colors = [
(Color::RED, (255, 0, 0)),
(Color::GREEN, (0, 255, 0)),
(Color::BLUE, (0, 0, 255)),
(Color::WHITE, (255, 255, 255)),
(Color::BLACK, (0, 0, 0)),
];
for (i, (color, (r, g, b))) in colors.iter().enumerate() {
fb.set_pixel(Point::new(i as i32, 0), *color);
assert_eq!(color.r(), *r);
assert_eq!(color.g(), *g);
assert_eq!(color.b(), *b);
}
}
#[test]
fn test_blanking_delay() {
let mut row: Row<TEST_COLS> = Row::new();
let test_addr = 5;
let prev_addr = 4;
row.format(test_addr, prev_addr);
let blanking_pixel_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 1);
assert_eq!(row.data[blanking_pixel_idx].output_enable(), false);
let before_blanking_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 2);
assert_eq!(row.data[before_blanking_idx].output_enable(), true);
}
#[test]
fn test_esp32_mapping() {
#[cfg(feature = "esp32-ordering")]
{
assert_eq!(map_index(0), 1);
assert_eq!(map_index(1), 0);
assert_eq!(map_index(2), 3);
assert_eq!(map_index(3), 2);
assert_eq!(map_index(4), 5);
assert_eq!(map_index(5), 4);
}
#[cfg(not(feature = "esp32-ordering"))]
{
assert_eq!(map_index(0), 0);
assert_eq!(map_index(1), 1);
assert_eq!(map_index(2), 2);
assert_eq!(map_index(3), 3);
}
}
#[test]
fn test_bits_assertion() {
assert!(TEST_BITS <= 8);
}
#[test]
fn test_fast_clear_method() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
fb.set_pixel_internal(20, 10, Color::GREEN);
let mapped_col_10 = get_mapped_index(10);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
fb.erase();
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
let last_col = get_mapped_index(TEST_COLS - 1);
assert_eq!(fb.frames[0].rows[5].data[last_col].latch(), true);
}
const CHAR_W: i32 = 6;
const CHAR_H: i32 = 10;
fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
use embedded_graphics::mock_display::MockDisplay;
use embedded_graphics::mono_font::ascii::FONT_6X10;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::text::{Baseline, Text};
let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
Text::with_baseline("A", origin, style, Baseline::Top)
.draw(fb)
.unwrap();
let mut reference: MockDisplay<Color> = MockDisplay::new();
Text::with_baseline("A", Point::zero(), style, Baseline::Top)
.draw(&mut reference)
.unwrap();
for dy in 0..CHAR_H {
for dx in 0..CHAR_W {
let expected_on = reference
.get_pixel(Point::new(dx, dy))
.unwrap_or(Color::BLACK)
!= Color::BLACK;
let gx = (origin.x + dx) as usize;
let gy = (origin.y + dy) as usize;
let frame0 = &fb.frames[0];
let e = if gy < TEST_NROWS {
&frame0.rows[gy].data[get_mapped_index(gx)]
} else {
&frame0.rows[gy - TEST_NROWS].data[get_mapped_index(gx)]
};
let (r, g, b) = if gy >= TEST_NROWS {
(e.red2(), e.grn2(), e.blu2())
} else {
(e.red1(), e.grn1(), e.blu1())
};
if expected_on {
assert!(r && g && b,);
} else {
assert!(!r && !g && !b);
}
}
}
}
#[test]
fn test_draw_char_corners() {
let upper_left = Point::new(0, 0);
let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
let mut fb = TestFrameBuffer::new();
verify_glyph_at(&mut fb, upper_left);
verify_glyph_at(&mut fb, lower_right);
}
#[test]
fn test_framebuffer_operations_trait_erase() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
fb.set_pixel_internal(20, 10, Color::GREEN);
<TestFrameBuffer as FrameBufferOperations<
TEST_ROWS,
TEST_COLS,
TEST_NROWS,
TEST_BITS,
TEST_FRAME_COUNT,
>>::erase(&mut fb);
let mc10 = get_mapped_index(10);
let mc20 = get_mapped_index(20);
assert_eq!(fb.frames[0].rows[5].data[mc10].red1(), false);
assert_eq!(fb.frames[0].rows[10].data[mc20].grn1(), false);
let last_col = get_mapped_index(TEST_COLS - 1);
assert!(fb.frames[0].rows[0].data[last_col].latch());
}
#[test]
fn test_framebuffer_operations_trait_set_pixel() {
let mut fb = TestFrameBuffer::new();
<TestFrameBuffer as FrameBufferOperations<
TEST_ROWS,
TEST_COLS,
TEST_NROWS,
TEST_BITS,
TEST_FRAME_COUNT,
>>::set_pixel(&mut fb, Point::new(8, 3), Color::BLUE);
let idx = get_mapped_index(8);
assert_eq!(fb.frames[0].rows[3].data[idx].blu1(), true);
assert_eq!(fb.frames[0].rows[3].data[idx].red1(), false);
assert_eq!(fb.frames[0].rows[3].data[idx].grn1(), false);
}
}