use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_framebuf::{FrameBuf, backends::DMACapableFrameBufferBackend};
pub use crate::present::PresentRegion as DisplayRegion;
#[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 AsyncDmaTransfer: DmaTransfer {
type WaitFuture: core::future::Future<Output = Self::Buffer>;
fn wait_async(self) -> Self::WaitFuture;
}
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")
}
}
impl<FB> core::future::Future for CompletedTransfer<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Output = FrameBuf<Rgb565, FB>;
fn poll(
self: core::pin::Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let buf = unsafe { self.get_unchecked_mut() }
.framebuffer
.take()
.expect("CompletedTransfer polled after completion");
core::task::Poll::Ready(buf)
}
}
impl<FB> AsyncDmaTransfer for CompletedTransfer<FB>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type WaitFuture = Self;
fn wait_async(self) -> Self::WaitFuture {
self
}
}
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()
}
}
impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> core::future::Future for RegionTransfer<FB> {
type Output = FrameBuf<Rgb565, FB>;
fn poll(
self: core::pin::Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
core::task::Poll::Ready(
unsafe { self.get_unchecked_mut() }
.framebuffer
.take()
.unwrap(),
)
}
}
impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> AsyncDmaTransfer for RegionTransfer<FB> {
type WaitFuture = Self;
fn wait_async(self) -> Self::WaitFuture {
self
}
}
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_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();
}
}