use core::{convert::Infallible, marker::PhantomData};
use crate::{Color, FrameBuffer, FrameBufferOperations, WordSize};
#[cfg(not(feature = "esp-hal-dma"))]
use embedded_dma::ReadBuffer;
use embedded_graphics::prelude::{DrawTarget, OriginDimensions, PixelColor, Point, Size};
#[cfg(feature = "esp-hal-dma")]
use esp_hal::dma::ReadBuffer;
#[must_use]
pub const fn compute_tiled_cols(
cols: usize,
num_panels_wide: usize,
num_panels_high: usize,
) -> usize {
cols * num_panels_wide * num_panels_high
}
pub trait PixelRemapper {
const VIRT_ROWS: usize;
const VIRT_COLS: usize;
const FB_ROWS: usize;
const FB_COLS: usize;
#[inline]
fn remap<C: PixelColor>(mut pixel: embedded_graphics::Pixel<C>) -> embedded_graphics::Pixel<C> {
pixel.0 = Self::remap_point(pixel.0);
pixel
}
#[inline]
#[must_use]
fn remap_point(mut point: Point) -> Point {
if point.x < 0 || point.y < 0 {
return point;
}
let (re_x, re_y) = Self::remap_xy(point.x as usize, point.y as usize);
point.x = i32::from(re_x as u16);
point.y = i32::from(re_y as u16);
point
}
fn remap_xy(x: usize, y: usize) -> (usize, usize);
#[inline]
#[must_use]
fn virtual_size() -> (usize, usize) {
(Self::VIRT_ROWS, Self::VIRT_COLS)
}
#[inline]
#[must_use]
fn fb_size() -> (usize, usize) {
(Self::FB_ROWS, Self::FB_COLS)
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct ChainTopRightDown<
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
> {}
impl<
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
> PixelRemapper for ChainTopRightDown<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
{
const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
const FB_ROWS: usize = PANEL_ROWS;
const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
fn remap_xy(x: usize, y: usize) -> (usize, usize) {
let row = y / PANEL_ROWS;
let base = (TILE_ROWS - 1 - row) * Self::VIRT_COLS;
if row % 2 == 1 {
(
base + Self::VIRT_COLS - 1 - x, PANEL_ROWS - 1 - (y % PANEL_ROWS), )
} else {
(base + x, y % PANEL_ROWS) }
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct TiledFrameBuffer<
F,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
>(F, PhantomData<M>);
impl<
F: Default,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
>
TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
#[must_use]
pub fn new() -> Self {
Self(F::default(), PhantomData)
}
}
impl<
F: Default,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> Default
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
fn default() -> Self {
Self::new()
}
}
impl<
F: DrawTarget<Error = Infallible, Color = Color>,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> DrawTarget
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
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>>,
{
self.0.draw_iter(pixels.into_iter().map(M::remap))
}
}
impl<
F: DrawTarget<Error = Infallible, Color = Color>,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> OriginDimensions
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
fn size(&self) -> Size {
Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
}
}
impl<
F: FrameBufferOperations<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
+ FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> FrameBufferOperations<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
#[inline]
fn erase(&mut self) {
self.0.erase();
}
#[inline]
fn set_pixel(&mut self, p: Point, color: Color) {
self.0.set_pixel(M::remap_point(p), color);
}
}
#[cfg(not(feature = "esp-hal-dma"))]
unsafe impl<
T,
F: ReadBuffer<Word = T>,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> ReadBuffer
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
type Word = T;
unsafe fn read_buffer(&self) -> (*const T, usize) {
self.0.read_buffer()
}
}
#[cfg(feature = "esp-hal-dma")]
unsafe impl<
F: ReadBuffer,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> ReadBuffer
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
unsafe fn read_buffer(&self) -> (*const u8, usize) {
self.0.read_buffer()
}
}
impl<
F: FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>,
M: PixelRemapper,
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
const FB_COLS: usize,
> FrameBuffer<PANEL_ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
fn get_word_size(&self) -> WordSize {
self.0.get_word_size()
}
}
#[cfg(test)]
mod tests {
extern crate std;
use embedded_graphics::prelude::*;
use super::*;
use core::convert::Infallible;
#[test]
fn test_virtual_size_function_with_equal_rows_and_cols() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
let virt_size = PanelChain::virtual_size();
assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL * 3));
}
#[test]
fn test_virtual_size_function_with_uneven_rows_and_cols() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
let virt_size = PanelChain::virtual_size();
assert_eq!(virt_size, (ROWS_IN_PANEL * 5, COLS_IN_PANEL * 3));
}
#[test]
fn test_virtual_size_function_with_single_column() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
let virt_size = PanelChain::virtual_size();
assert_eq!(virt_size, (ROWS_IN_PANEL * 3, COLS_IN_PANEL));
}
#[test]
fn test_fb_size_function_with_equal_rows_and_cols() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 3>;
let virt_size = PanelChain::fb_size();
assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 9));
}
#[test]
fn test_fb_size_function_with_uneven_rows_and_cols() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 5, 3>;
let virt_size = PanelChain::fb_size();
assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 15));
}
#[test]
fn test_fb_size_function_with_single_column() {
const ROWS_IN_PANEL: usize = 32;
const COLS_IN_PANEL: usize = 64;
type PanelChain = ChainTopRightDown<ROWS_IN_PANEL, COLS_IN_PANEL, 3, 1>;
let virt_size = PanelChain::fb_size();
assert_eq!(virt_size, (ROWS_IN_PANEL, COLS_IN_PANEL * 3));
}
#[test]
fn test_pixel_remap_top_right_down_point_in_origin() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(0, 0), Color::RED));
assert_eq!(pixel.0, Point::new(384, 0));
}
#[test]
fn test_pixel_remap_top_right_down_point_in_bottom_left_corner() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(0, 95), Color::RED));
assert_eq!(pixel.0, Point::new(0, 31));
}
#[test]
fn test_pixel_remap_top_right_down_point_in_bottom_right_corner() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(191, 95), Color::RED));
assert_eq!(pixel.0, Point::new(191, 31));
}
#[test]
fn test_pixel_remap_top_right_down_point_on_x_right_edge_of_first_panel() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(63, 0), Color::RED));
assert_eq!(pixel.0, Point::new(447, 0));
}
#[test]
fn test_pixel_remap_top_right_down_point_on_x_left_edge_of_second_panel() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(64, 0), Color::RED));
assert_eq!(pixel.0, Point::new(448, 0));
}
#[test]
fn test_pixel_remap_top_right_down_point_on_y_bottom_edge_of_first_panel() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(0, 31), Color::RED));
assert_eq!(pixel.0, Point::new(384, 31));
}
#[test]
fn test_pixel_remap_top_right_down_point_on_y_top_edge_of_fourth_panel() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(0, 32), Color::RED));
assert_eq!(pixel.0, Point::new(383, 31));
}
#[test]
fn test_pixel_remap_top_right_down_point_slightly_to_the_top_middle() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(100, 40), Color::RED));
assert_eq!(pixel.0, Point::new(283, 23));
}
#[test]
fn test_pixel_remap_negative_pixel_does_not_remap() {
type PanelChain = ChainTopRightDown<32, 64, 3, 3>;
let pixel = PanelChain::remap(Pixel(Point::new(-5, 40), Color::RED));
assert_eq!(pixel.0, Point::new(-5, 40));
}
#[test]
fn test_compute_tiled_cols() {
assert_eq!(192, compute_tiled_cols(32, 3, 2));
}
#[test]
fn test_tiling_framebuffer_canvas_size() {
use crate::plain::DmaFrameBuffer;
use crate::tiling::{compute_tiled_cols, ChainTopRightDown, TiledFrameBuffer};
use crate::{compute_frame_count, compute_rows};
const TILED_COLS: usize = 3;
const TILED_ROWS: usize = 3;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
const BITS: u8 = 2;
const NROWS: usize = compute_rows(ROWS);
const FRAME_COUNT: usize = compute_frame_count(BITS);
type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
type TiledFBType = TiledFrameBuffer<
FBType,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILED_ROWS,
TILED_COLS,
FB_COLS,
>;
let fb = TiledFBType::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
struct TestFrameBuffer {
calls: std::cell::RefCell<std::vec::Vec<Call>>,
buf: [u8; 8],
word_size: WordSize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Call {
Erase,
SetPixel { p: Point, color: Color },
Draw(std::vec::Vec<(Point, Color)>),
}
impl TestFrameBuffer {
fn new(word_size: WordSize) -> Self {
Self {
calls: std::cell::RefCell::new(std::vec::Vec::new()),
buf: [0; 8],
word_size,
}
}
fn take_calls(&self) -> std::vec::Vec<Call> {
core::mem::take(&mut *self.calls.borrow_mut())
}
}
impl Default for TestFrameBuffer {
fn default() -> Self {
Self::new(WordSize::Eight)
}
}
impl DrawTarget for TestFrameBuffer {
type Color = Color;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
let v = pixels.into_iter().map(|p| (p.0, p.1)).collect();
self.calls.borrow_mut().push(Call::Draw(v));
Ok(())
}
}
impl OriginDimensions for TestFrameBuffer {
fn size(&self) -> Size {
Size::new(1, 1)
}
}
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 TestFrameBuffer
{
fn get_word_size(&self) -> WordSize {
self.word_size
}
}
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 TestFrameBuffer
{
fn erase(&mut self) {
self.calls.borrow_mut().push(Call::Erase);
}
fn set_pixel(&mut self, p: Point, color: Color) {
self.calls.borrow_mut().push(Call::SetPixel { p, color });
}
}
#[cfg(not(feature = "esp-hal-dma"))]
unsafe impl embedded_dma::ReadBuffer for TestFrameBuffer {
type Word = u8;
unsafe fn read_buffer(&self) -> (*const u8, usize) {
(self.buf.as_ptr(), self.buf.len())
}
}
#[cfg(feature = "esp-hal-dma")]
unsafe impl esp_hal::dma::ReadBuffer for TestFrameBuffer {
unsafe fn read_buffer(&self) -> (*const u8, usize) {
(self.buf.as_ptr(), self.buf.len())
}
}
#[test]
fn test_tiled_draw_iter_forwards_with_remap() {
const TILED_COLS: usize = 3;
const TILED_ROWS: usize = 3;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let mut fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
let input = [
Pixel(Point::new(0, 0), Color::RED),
Pixel(Point::new(63, 0), Color::GREEN),
Pixel(Point::new(64, 0), Color::BLUE),
Pixel(Point::new(100, 40), Color::WHITE),
];
fb.draw_iter(input.into_iter()).unwrap();
let calls = fb.0.take_calls();
assert_eq!(calls.len(), 1);
match &calls[0] {
Call::Draw(v) => {
let expected =
[
ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
Pixel(Point::new(0, 0), Color::RED),
),
ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
Pixel(Point::new(63, 0), Color::GREEN),
),
ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
Pixel(Point::new(64, 0), Color::BLUE),
),
ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap(
Pixel(Point::new(100, 40), Color::WHITE),
),
];
let expected_points: std::vec::Vec<(Point, Color)> =
expected.iter().map(|p| (p.0, p.1)).collect();
assert_eq!(v.as_slice(), expected_points.as_slice());
}
_ => panic!("expected a Draw call"),
}
}
#[test]
fn test_tiled_set_pixel_remaps_and_forwards() {
const TILED_COLS: usize = 3;
const TILED_ROWS: usize = 3;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let mut fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
let p = Point::new(100, 40);
fb.set_pixel(p, Color::BLUE);
let calls = fb.0.take_calls();
assert_eq!(calls.len(), 1);
match calls.into_iter().next().unwrap() {
Call::SetPixel { p: rp, color } => {
let expected =
ChainTopRightDown::<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>::remap_point(p);
assert_eq!(rp, expected);
assert_eq!(color, Color::BLUE);
}
_ => panic!("expected a SetPixel call"),
}
}
#[test]
fn test_tiled_erase_forwards() {
const TILED_COLS: usize = 2;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let mut fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
fb.erase();
let calls = fb.0.take_calls();
assert_eq!(calls, std::vec![Call::Erase]);
}
#[test]
fn test_tiled_negative_coordinates_not_remapped() {
const TILED_COLS: usize = 2;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let mut fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
let neg = Point::new(-3, 5);
fb.set_pixel(neg, Color::GREEN);
fb.draw_iter(core::iter::once(Pixel(Point::new(10, -2), Color::RED)))
.unwrap();
let calls = fb.0.take_calls();
assert_eq!(calls.len(), 2);
assert!(matches!(calls[0], Call::SetPixel { p, .. } if p == neg));
match &calls[1] {
Call::Draw(v) => {
assert_eq!(v.as_slice(), &[(Point::new(10, -2), Color::RED)]);
}
_ => panic!("expected a Draw call"),
}
}
#[test]
fn test_tiled_read_buffer_passthrough() {
const TILED_COLS: usize = 2;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
let inner_ptr = fb.0.buf.as_ptr();
let inner_len = fb.0.buf.len();
let (ptr, len) = unsafe { fb.read_buffer() };
assert_eq!(ptr, inner_ptr);
assert_eq!(len, inner_len);
}
#[test]
fn test_tiled_get_word_size_passthrough() {
const TILED_COLS: usize = 2;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Sixteen),
core::marker::PhantomData,
);
assert_eq!(fb.get_word_size(), WordSize::Sixteen);
}
#[test]
fn test_tiled_get_word_size_eight_passthrough() {
const TILED_COLS: usize = 2;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
assert_eq!(fb.get_word_size(), WordSize::Eight);
}
struct Huge<
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
>;
impl<
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
const TILE_ROWS: usize,
const TILE_COLS: usize,
> PixelRemapper for Huge<PANEL_ROWS, PANEL_COLS, TILE_ROWS, TILE_COLS>
{
const VIRT_ROWS: usize = PANEL_ROWS * TILE_ROWS;
const VIRT_COLS: usize = PANEL_COLS * TILE_COLS;
const FB_ROWS: usize = PANEL_ROWS;
const FB_COLS: usize = PANEL_COLS * TILE_ROWS * TILE_COLS;
fn remap_xy(x: usize, y: usize) -> (usize, usize) {
(x + 70_000, y + 70_000)
}
}
#[test]
fn test_remap_point_truncates_to_u16_range() {
const TILED_COLS: usize = 1;
const TILED_ROWS: usize = 1;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let mut fb = TiledFrameBuffer::<
TestFrameBuffer,
Huge<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>(
TestFrameBuffer::new(WordSize::Eight),
core::marker::PhantomData,
);
fb.set_pixel(Point::new(1, 2), Color::RED);
let calls = fb.0.take_calls();
match calls.into_iter().next().unwrap() {
Call::SetPixel { p, color } => {
let (rx, ry) = (1usize + 70_000, 2usize + 70_000);
let expected = Point::new(i32::from(rx as u16), i32::from(ry as u16));
assert_eq!(p, expected);
assert_eq!(color, Color::RED);
}
other => panic!("unexpected call recorded: {other:?}"),
}
}
#[test]
fn test_more_compute_tiled_cols_cases() {
assert_eq!(compute_tiled_cols(64, 1, 4), 256);
assert_eq!(compute_tiled_cols(64, 4, 1), 256);
assert_eq!(compute_tiled_cols(32, 4, 5), 640);
}
#[test]
fn test_tiled_default_and_new_construct() {
const TILED_COLS: usize = 4;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let fb_default = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>::default();
let fb_new = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>::new();
assert_eq!(fb_default.get_word_size(), WordSize::Eight);
assert_eq!(fb_new.get_word_size(), WordSize::Eight);
let expected_size = Size::new((PANEL_COLS * TILED_COLS) as u32, (ROWS * TILED_ROWS) as u32);
assert_eq!(fb_default.size(), expected_size);
assert_eq!(fb_new.size(), expected_size);
assert!(fb_default.0.take_calls().is_empty());
assert!(fb_new.0.take_calls().is_empty());
}
#[test]
fn test_tiled_origin_dimensions_matches_virtual_size() {
const TILED_COLS: usize = 5;
const TILED_ROWS: usize = 2;
const ROWS: usize = 32;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
let fb = TiledFrameBuffer::<
TestFrameBuffer,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
ROWS,
PANEL_COLS,
{ crate::compute_rows(ROWS) },
2,
{ crate::compute_frame_count(2) },
TILED_ROWS,
TILED_COLS,
FB_COLS,
>::new();
let (virt_rows, virt_cols) = <ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS> as PixelRemapper>::virtual_size();
assert_eq!(fb.size(), Size::new(virt_cols as u32, virt_rows as u32));
}
fn expected_ctrdd_xy<const PR: usize, const PC: usize, const TR: usize, const TC: usize>(
x: usize,
y: usize,
) -> (usize, usize) {
let row = y / PR;
let base = (TR - 1 - row) * (PC * TC);
if row % 2 == 1 {
(base + (PC * TC) - 1 - x, PR - 1 - (y % PR))
} else {
(base + x, y % PR)
}
}
#[test]
fn test_chain_top_right_down_corners_2x3() {
const PR: usize = 32;
const PC: usize = 64;
const TR: usize = 2;
const TC: usize = 3;
type M = ChainTopRightDown<PR, PC, TR, TC>;
for r in 0..TR {
for c in 0..TC {
let x0 = c * PC;
let y0 = r * PR;
let corners = [
(x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
for &(x, y) in &corners {
let got = <M as PixelRemapper>::remap_xy(x, y);
let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
assert_eq!(
got, exp,
"corner mismatch at panel (row={}, col={}), virtual=({}, {})",
r, c, x, y
);
}
}
}
}
#[test]
fn test_chain_top_right_down_corners_3x2() {
const PR: usize = 32;
const PC: usize = 64;
const TR: usize = 3;
const TC: usize = 2;
type M = ChainTopRightDown<PR, PC, TR, TC>;
for r in 0..TR {
for c in 0..TC {
let x0 = c * PC;
let y0 = r * PR;
let corners = [
(x0, y0), (x0 + PC - 1, y0), (x0, y0 + PR - 1), (x0 + PC - 1, y0 + PR - 1), ];
for &(x, y) in &corners {
let got = <M as PixelRemapper>::remap_xy(x, y);
let exp = expected_ctrdd_xy::<PR, PC, TR, TC>(x, y);
assert_eq!(
got, exp,
"corner mismatch at panel (row={}, col={}), virtual=({}, {})",
r, c, x, y
);
}
}
}
}
}