use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_framebuf::{FrameBuf, backends::DMACapableFrameBufferBackend};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisplayRegion {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}
impl DisplayRegion {
pub const fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
Self {
x,
y,
width,
height,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayError {
Busy,
HardwareError,
InvalidBuffer,
}
pub struct TransferError<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
pub framebuffer: FrameBuf<Rgb565, FB>,
pub error: DisplayError,
}
impl<FB> core::fmt::Debug for TransferError<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TransferError")
.field("error", &self.error)
.finish_non_exhaustive()
}
}
pub trait DmaTransfer {
type Buffer;
fn is_done(&self) -> bool;
fn wait(self) -> Self::Buffer;
}
pub trait DisplayBackend<const W: usize, const H: usize, FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Transfer: DmaTransfer<Buffer = FrameBuf<Rgb565, FB>>;
fn start_dma_transfer(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
) -> Result<Self::Transfer, TransferError<FB>>;
fn start_dma_transfer_region(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
_region: DisplayRegion,
) -> Result<Self::Transfer, TransferError<FB>> {
self.start_dma_transfer(framebuffer)
}
}
pub struct CompletedTransfer<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
framebuffer: Option<FrameBuf<Rgb565, FB>>,
}
impl<FB> DmaTransfer for CompletedTransfer<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Buffer = FrameBuf<Rgb565, FB>;
fn is_done(&self) -> bool {
true
}
fn wait(mut self) -> FrameBuf<Rgb565, FB> {
self.framebuffer
.take()
.expect("CompletedTransfer polled after completion")
}
}
pub struct SimulatorBackend;
impl SimulatorBackend {
pub fn new() -> Self {
Self
}
}
impl Default for SimulatorBackend {
fn default() -> Self {
Self::new()
}
}
impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for SimulatorBackend
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Transfer = CompletedTransfer<FB>;
fn start_dma_transfer(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
) -> Result<CompletedTransfer<FB>, TransferError<FB>> {
Ok(CompletedTransfer {
framebuffer: Some(framebuffer),
})
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use core::cell::Cell;
use embedded_graphics_core::pixelcolor::RgbColor;
use embedded_graphics_framebuf::backends::EndianCorrectedBuffer;
use std::vec;
type TestBackend = EndianCorrectedBuffer<'static, Rgb565>;
fn make_fb<const W: usize, const H: usize>() -> FrameBuf<Rgb565, TestBackend> {
use embedded_graphics_framebuf::backends::EndianCorrection;
let data: &'static mut [Rgb565] = vec![Rgb565::BLACK; W * H].leak();
FrameBuf::new(
EndianCorrectedBuffer::new(data, EndianCorrection::ToLittleEndian),
W,
H,
)
}
struct RegionTransfer<FB: DMACapableFrameBufferBackend<Color = Rgb565>> {
framebuffer: Option<FrameBuf<Rgb565, FB>>,
}
impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> DmaTransfer for RegionTransfer<FB> {
type Buffer = FrameBuf<Rgb565, FB>;
fn is_done(&self) -> bool {
true
}
fn wait(mut self) -> FrameBuf<Rgb565, FB> {
self.framebuffer.take().unwrap()
}
}
struct RegionTrackingBackend {
region_calls: Cell<usize>,
}
impl RegionTrackingBackend {
fn new() -> Self {
Self {
region_calls: Cell::new(0),
}
}
}
impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for RegionTrackingBackend
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Transfer = RegionTransfer<FB>;
fn start_dma_transfer(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
) -> Result<RegionTransfer<FB>, TransferError<FB>> {
Ok(RegionTransfer {
framebuffer: Some(framebuffer),
})
}
fn start_dma_transfer_region(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
_region: DisplayRegion,
) -> Result<RegionTransfer<FB>, TransferError<FB>> {
self.region_calls.set(self.region_calls.get() + 1);
Ok(RegionTransfer {
framebuffer: Some(framebuffer),
})
}
}
#[test]
fn test_simulator_backend_transfer_completes_immediately() {
let mut backend = SimulatorBackend::new();
let fb = make_fb::<2, 2>();
let xfer = <SimulatorBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
&mut backend,
fb,
)
.unwrap();
assert!(xfer.is_done());
let _fb = xfer.wait();
}
#[test]
fn test_simulator_backend_returns_buffer_on_wait() {
let mut backend = SimulatorBackend::new();
let fb = make_fb::<4, 4>();
let xfer = <SimulatorBackend as DisplayBackend<4, 4, TestBackend>>::start_dma_transfer(
&mut backend,
fb,
)
.unwrap();
let fb_back = xfer.wait();
assert_eq!(fb_back.width(), 4);
assert_eq!(fb_back.height(), 4);
}
#[test]
fn test_region_tracking_backend_counts_region_transfers() {
let mut backend = RegionTrackingBackend::new();
let fb = make_fb::<2, 2>();
let region = DisplayRegion::new(0, 0, 1, 1);
let xfer = <RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer_region(
&mut backend,
fb,
region,
)
.unwrap();
assert_eq!(backend.region_calls.get(), 1);
let _ = xfer.wait();
}
#[test]
fn test_full_transfer_does_not_increment_region_counter() {
let mut backend = RegionTrackingBackend::new();
let fb = make_fb::<2, 2>();
let xfer =
<RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
&mut backend,
fb,
)
.unwrap();
assert_eq!(backend.region_calls.get(), 0);
let _ = xfer.wait();
}
}