use core::{convert::Infallible, marker::PhantomData};
use crate::{
BcmLenReps, Color, FrameBuffer, FrameBufferOperations, MutableFrameBuffer,
BCM_SEQUENCE_CAPACITY,
};
use embedded_graphics::prelude::{DrawTarget, OriginDimensions, PixelColor, Point, Size};
#[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) }
}
}
pub mod quarter_scan {
pub trait Variant {
const SLOT: [(usize, usize); 4];
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct Linear;
impl Variant for Linear {
const SLOT: [(usize, usize); 4] = [(0, 0), (0, 1), (1, 0), (1, 1)];
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct SectionsSwapped;
impl Variant for SectionsSwapped {
const SLOT: [(usize, usize); 4] = [(0, 1), (0, 0), (1, 1), (1, 0)];
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct HalvesSwapped;
impl Variant for HalvesSwapped {
const SLOT: [(usize, usize); 4] = [(1, 0), (1, 1), (0, 0), (0, 1)];
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct Alternating;
impl Variant for Alternating {
const SLOT: [(usize, usize); 4] = [(0, 0), (1, 0), (0, 1), (1, 1)];
}
}
#[derive(core::fmt::Debug)]
pub struct QuarterScan<
const PANEL_ROWS: usize,
const PANEL_COLS: usize,
V: quarter_scan::Variant = quarter_scan::SectionsSwapped,
> {
_variant: PhantomData<V>,
}
#[cfg(feature = "defmt")]
impl<const PANEL_ROWS: usize, const PANEL_COLS: usize, V: quarter_scan::Variant> defmt::Format
for QuarterScan<PANEL_ROWS, PANEL_COLS, V>
{
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "QuarterScan");
}
}
impl<const PANEL_ROWS: usize, const PANEL_COLS: usize, V: quarter_scan::Variant> PixelRemapper
for QuarterScan<PANEL_ROWS, PANEL_COLS, V>
{
const VIRT_ROWS: usize = PANEL_ROWS;
const VIRT_COLS: usize = PANEL_COLS;
const FB_ROWS: usize = PANEL_ROWS / 2;
const FB_COLS: usize = PANEL_COLS * 2;
fn remap_xy(x: usize, y: usize) -> (usize, usize) {
if x >= Self::VIRT_COLS || y >= Self::VIRT_ROWS {
return (Self::FB_COLS, Self::FB_ROWS);
}
let group_rows = PANEL_ROWS / 4;
let group = y / group_rows;
let addr = y % group_rows;
let (channel, section) = V::SLOT[group];
(section * PANEL_COLS + x, channel * group_rows + addr)
}
}
#[deprecated(
since = "0.11.0",
note = "use RemappedFrameBuffer<F, M> instead -- it works with all framebuffer types and has a simpler signature"
)]
#[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>);
#[allow(deprecated)]
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)
}
}
#[allow(deprecated)]
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()
}
}
#[allow(deprecated)]
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))
}
}
#[allow(deprecated)]
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)
}
}
#[allow(deprecated)]
impl<
F: FrameBufferOperations + FrameBuffer,
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
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);
}
}
#[allow(deprecated)]
impl<
F: FrameBuffer,
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
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
type Word = F::Word;
const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = F::BCM_SEQUENCE;
const BCM_SEQUENCE_LEN: usize = F::BCM_SEQUENCE_LEN;
const BCM_SEQUENCE_COUNT: usize = F::BCM_SEQUENCE_COUNT;
const BCM_SEGMENTS_PER_GROUP: usize = F::BCM_SEGMENTS_PER_GROUP;
fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
self.0.bcm_segment_ptr(index)
}
}
#[allow(deprecated)]
impl<
F: MutableFrameBuffer,
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,
> MutableFrameBuffer
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
}
#[cfg(feature = "defmt")]
#[allow(deprecated)]
impl<
F: defmt::Format,
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,
> defmt::Format
for TiledFrameBuffer<
F,
M,
PANEL_ROWS,
PANEL_COLS,
NROWS,
BITS,
FRAME_COUNT,
TILE_ROWS,
TILE_COLS,
FB_COLS,
>
{
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "TiledFrameBuffer({:?})", self.0);
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(core::fmt::Debug)]
pub struct RemappedFrameBuffer<F, M: PixelRemapper>(F, PhantomData<M>);
impl<F: Default, M: PixelRemapper> RemappedFrameBuffer<F, M> {
#[must_use]
pub fn new() -> Self {
Self(F::default(), PhantomData)
}
}
impl<F: Default, M: PixelRemapper> Default for RemappedFrameBuffer<F, M> {
fn default() -> Self {
Self::new()
}
}
impl<F: DrawTarget<Error = Infallible, Color = Color>, M: PixelRemapper> DrawTarget
for RemappedFrameBuffer<F, M>
{
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> OriginDimensions
for RemappedFrameBuffer<F, M>
{
fn size(&self) -> Size {
Size::new(M::virtual_size().1 as u32, M::virtual_size().0 as u32)
}
}
impl<F: FrameBufferOperations + FrameBuffer, M: PixelRemapper> FrameBufferOperations
for RemappedFrameBuffer<F, M>
{
#[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);
}
}
impl<F: FrameBuffer, M: PixelRemapper> FrameBuffer for RemappedFrameBuffer<F, M> {
type Word = F::Word;
const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = F::BCM_SEQUENCE;
const BCM_SEQUENCE_LEN: usize = F::BCM_SEQUENCE_LEN;
const BCM_SEQUENCE_COUNT: usize = F::BCM_SEQUENCE_COUNT;
const BCM_SEGMENTS_PER_GROUP: usize = F::BCM_SEGMENTS_PER_GROUP;
fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
self.0.bcm_segment_ptr(index)
}
}
impl<F: MutableFrameBuffer, M: PixelRemapper> MutableFrameBuffer for RemappedFrameBuffer<F, M> {}
#[cfg(test)]
mod tests {
extern crate std;
use embedded_graphics::prelude::*;
use super::*;
use crate::MutableFrameBuffer;
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]
#[allow(deprecated)]
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],
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Call {
Erase,
SetPixel { p: Point, color: Color },
Draw(std::vec::Vec<(Point, Color)>),
}
impl TestFrameBuffer {
fn new() -> Self {
Self {
calls: std::cell::RefCell::new(std::vec::Vec::new()),
buf: [0; 8],
}
}
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()
}
}
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 FrameBuffer for TestFrameBuffer {
type Word = u8;
const BCM_SEQUENCE: [crate::BcmLenReps; crate::BCM_SEQUENCE_CAPACITY] = {
let mut seq = [crate::BcmLenReps::ZERO; crate::BCM_SEQUENCE_CAPACITY];
seq[0] = crate::BcmLenReps { len: 8, reps: 1 };
seq
};
const BCM_SEQUENCE_LEN: usize = 1;
const BCM_SEQUENCE_COUNT: usize = 1;
fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
assert!(index == 0);
self.buf.as_ptr()
}
}
impl FrameBufferOperations 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 });
}
}
impl MutableFrameBuffer for TestFrameBuffer {}
#[test]
#[allow(deprecated)]
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(), 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]
#[allow(deprecated)]
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(), 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]
#[allow(deprecated)]
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(), core::marker::PhantomData);
fb.erase();
let calls = fb.0.take_calls();
assert_eq!(calls, std::vec![Call::Erase]);
}
#[test]
#[allow(deprecated)]
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(), 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"),
}
}
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]
#[allow(deprecated)]
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(), 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]
#[allow(deprecated)]
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();
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]
#[allow(deprecated)]
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
);
}
}
}
}
type TestRemapped = RemappedFrameBuffer<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>;
#[test]
fn test_remapped_default_and_new_construct() {
let _fb = TestRemapped::new();
let _fb2: TestRemapped = Default::default();
}
#[test]
fn test_remapped_origin_dimensions_matches_virtual_size() {
let fb = TestRemapped::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
#[test]
fn test_remapped_draw_iter_forwards_with_remap() {
let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>(
TestFrameBuffer::new(),
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::<32, 64, 3, 3>::remap(Pixel(Point::new(0, 0), Color::RED)),
ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(
Point::new(63, 0),
Color::GREEN,
)),
ChainTopRightDown::<32, 64, 3, 3>::remap(Pixel(Point::new(64, 0), Color::BLUE)),
ChainTopRightDown::<32, 64, 3, 3>::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_remapped_set_pixel_remaps_and_forwards() {
let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 3, 3>>(
TestFrameBuffer::new(),
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::<32, 64, 3, 3>::remap_point(p);
assert_eq!(rp, expected);
assert_eq!(color, Color::BLUE);
}
_ => panic!("expected a SetPixel call"),
}
}
#[test]
fn test_remapped_erase_forwards() {
let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 2, 2>>(
TestFrameBuffer::new(),
core::marker::PhantomData,
);
fb.erase();
let calls = fb.0.take_calls();
assert_eq!(calls, std::vec![Call::Erase]);
}
#[test]
fn test_remapped_negative_coordinates_not_remapped() {
let mut fb = RemappedFrameBuffer::<TestFrameBuffer, ChainTopRightDown<32, 64, 2, 2>>(
TestFrameBuffer::new(),
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_remapped_with_plain_framebuffer() {
use crate::plain::DmaFrameBuffer;
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 = crate::compute_rows(ROWS);
const FRAME_COUNT: usize = crate::compute_frame_count(BITS);
type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
type Display = RemappedFrameBuffer<
FBType,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
>;
let fb = Display::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
#[test]
fn test_remapped_with_latched_framebuffer() {
use crate::latched::DmaFrameBuffer;
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 = crate::compute_rows(ROWS);
const FRAME_COUNT: usize = crate::compute_frame_count(BITS);
type FBType = DmaFrameBuffer<ROWS, FB_COLS, NROWS, BITS, FRAME_COUNT>;
type Display = RemappedFrameBuffer<
FBType,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
>;
let fb = Display::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
#[test]
fn test_remapped_with_bitplane_plain_framebuffer() {
use crate::bitplane::plain::DmaFrameBuffer;
const TILED_COLS: usize = 3;
const TILED_ROWS: usize = 3;
const ROWS: usize = 32;
const NROWS: usize = ROWS / 2;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
const PLANES: usize = 8;
type FBType = DmaFrameBuffer<NROWS, FB_COLS, PLANES>;
type Display = RemappedFrameBuffer<
FBType,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
>;
let fb = Display::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
#[test]
fn test_remapped_with_bitplane_latched_framebuffer() {
use crate::bitplane::latched::DmaFrameBuffer;
const TILED_COLS: usize = 3;
const TILED_ROWS: usize = 3;
const ROWS: usize = 32;
const NROWS: usize = ROWS / 2;
const PANEL_COLS: usize = 64;
const FB_COLS: usize = compute_tiled_cols(PANEL_COLS, TILED_ROWS, TILED_COLS);
const PLANES: usize = 8;
type FBType = DmaFrameBuffer<NROWS, FB_COLS, PLANES>;
type Display = RemappedFrameBuffer<
FBType,
ChainTopRightDown<ROWS, PANEL_COLS, TILED_ROWS, TILED_COLS>,
>;
let fb = Display::new();
assert_eq!(fb.size(), Size::new(192, 96));
}
#[test]
fn test_quarter_scan_associated_constants() {
type QS = QuarterScan<64, 64>;
assert_eq!(QS::VIRT_ROWS, 64);
assert_eq!(QS::VIRT_COLS, 64);
assert_eq!(QS::FB_ROWS, 32);
assert_eq!(QS::FB_COLS, 128);
assert_eq!(QS::virtual_size(), (64, 64));
assert_eq!(QS::fb_size(), (32, 128));
}
#[test]
fn test_quarter_scan_remap_xy_groups() {
type QS = QuarterScan<64, 64>;
assert_eq!(QS::remap_xy(0, 0), (64, 0));
assert_eq!(QS::remap_xy(63, 15), (127, 15));
assert_eq!(QS::remap_xy(0, 16), (0, 0));
assert_eq!(QS::remap_xy(63, 31), (63, 15));
assert_eq!(QS::remap_xy(0, 32), (64, 16));
assert_eq!(QS::remap_xy(63, 47), (127, 31));
assert_eq!(QS::remap_xy(0, 48), (0, 16));
assert_eq!(QS::remap_xy(63, 63), (63, 31));
}
#[test]
fn test_quarter_scan_remap_out_of_bounds_is_clipped() {
type QS = QuarterScan<64, 64>;
assert_eq!(QS::remap_xy(64, 0), (128, 32));
assert_eq!(QS::remap_xy(0, 64), (128, 32));
assert_eq!(QS::remap_xy(100, 100), (128, 32));
assert_eq!(QS::remap_xy(63, 63), (63, 31));
}
#[test]
fn test_quarter_scan_offscreen_pixels_do_not_panic() {
use crate::bitplane::plain::DmaFrameBuffer;
type InnerFB = DmaFrameBuffer<16, 128, 4>;
type Display = RemappedFrameBuffer<InnerFB, QuarterScan<64, 64>>;
let mut fb = Display::new();
for x in 0..70 {
for y in 57..70 {
fb.set_pixel(Point::new(x, y), Color::WHITE);
}
}
}
#[test]
fn test_quarter_scan_remap_is_bijective_and_in_bounds() {
use super::quarter_scan::*;
fn check<V: Variant>() {
let mut seen = [false; 32 * 128];
for y in 0..QuarterScan::<64, 64, V>::VIRT_ROWS {
for x in 0..QuarterScan::<64, 64, V>::VIRT_COLS {
let (fb_x, fb_y) = QuarterScan::<64, 64, V>::remap_xy(x, y);
assert!(
fb_x < QuarterScan::<64, 64, V>::FB_COLS,
"fb_x {fb_x} out of bounds"
);
assert!(
fb_y < QuarterScan::<64, 64, V>::FB_ROWS,
"fb_y {fb_y} out of bounds"
);
let idx = fb_y * QuarterScan::<64, 64, V>::FB_COLS + fb_x;
assert!(!seen[idx], "duplicate mapping to ({fb_x}, {fb_y})");
seen[idx] = true;
}
}
assert!(seen.iter().all(|&s| s));
}
check::<Linear>();
check::<SectionsSwapped>();
check::<HalvesSwapped>();
check::<Alternating>();
}
#[test]
fn test_quarter_scan_variant_tables_are_permutations() {
use super::quarter_scan::*;
fn assert_permutation<V: Variant>() {
let mut seen = [false; 4];
for &(channel, section) in V::SLOT.iter() {
assert!(channel < 2 && section < 2, "slot out of range");
let idx = channel * 2 + section;
assert!(!seen[idx], "duplicate slot in variant table");
seen[idx] = true;
}
assert!(
seen.iter().all(|&s| s),
"variant table must cover all four slots"
);
}
assert_permutation::<Linear>();
assert_permutation::<SectionsSwapped>();
assert_permutation::<HalvesSwapped>();
assert_permutation::<Alternating>();
}
#[test]
fn test_quarter_scan_variant_mappings() {
use super::quarter_scan::*;
assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 0), (0, 0));
assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 16), (64, 0));
assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 32), (0, 16));
assert_eq!(QuarterScan::<64, 64, Linear>::remap_xy(0, 48), (64, 16));
assert_eq!(
QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 0),
(0, 16)
);
assert_eq!(
QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 16),
(64, 16)
);
assert_eq!(
QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 32),
(0, 0)
);
assert_eq!(
QuarterScan::<64, 64, HalvesSwapped>::remap_xy(0, 48),
(64, 0)
);
assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 0), (0, 0));
assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 16), (0, 16));
assert_eq!(QuarterScan::<64, 64, Alternating>::remap_xy(0, 32), (64, 0));
assert_eq!(
QuarterScan::<64, 64, Alternating>::remap_xy(0, 48),
(64, 16)
);
}
#[test]
fn test_quarter_scan_remap_matches_inner_bitplane_geometry() {
use crate::bitplane::plain::DmaFrameBuffer;
type QS = QuarterScan<64, 64>;
type InnerFB = DmaFrameBuffer<16, 128, 4>;
type Display = RemappedFrameBuffer<InnerFB, QS>;
let mut fb = Display::new();
assert_eq!(fb.size(), Size::new(64, 64));
fb.set_pixel(Point::new(1, 2), Color::RED);
fb.set_pixel(Point::new(1, 20), Color::GREEN);
fb.set_pixel(Point::new(1, 40), Color::BLUE);
fb.set_pixel(Point::new(1, 60), Color::WHITE);
}
}