use crate::display_backend::{DisplayBackend, DisplayError, DisplayRegion, DmaTransfer};
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_framebuf::{
FrameBuf,
backends::{DMACapableFrameBufferBackend, EndianCorrectedBuffer, EndianCorrection},
};
enum FrontState<FB, Xfer> {
Idle(FB),
InFlight(Xfer),
}
impl<FB, Xfer: DmaTransfer<Buffer = FB>> FrontState<FB, Xfer> {
fn recover(self) -> FB {
match self {
FrontState::Idle(fb) => fb,
FrontState::InFlight(xfer) => xfer.wait(),
}
}
fn is_ready(&self) -> bool {
match self {
FrontState::Idle(_) => true,
FrontState::InFlight(xfer) => xfer.is_done(),
}
}
}
pub struct SwapChain<const W: usize, const H: usize, FB, B>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
B: DisplayBackend<W, H, FB>,
{
back: FrameBuf<Rgb565, FB>,
front: Option<FrontState<FrameBuf<Rgb565, FB>, B::Transfer>>,
backend: B,
frame_count: u64,
}
pub type StandardSwapChain<const W: usize, const H: usize, B> =
SwapChain<W, H, EndianCorrectedBuffer<'static, Rgb565>, B>;
impl<const W: usize, const H: usize, B> StandardSwapChain<W, H, B>
where
B: DisplayBackend<W, H, EndianCorrectedBuffer<'static, Rgb565>>,
{
pub fn from_static_slices(
front_data: &'static mut [Rgb565],
back_data: &'static mut [Rgb565],
big_endian: bool,
backend: B,
) -> Self {
let mk_buf = |data: &'static mut [Rgb565]| {
let correction = if big_endian {
EndianCorrection::ToBigEndian
} else {
EndianCorrection::ToLittleEndian
};
EndianCorrectedBuffer::new(data, correction)
};
let front_fb = FrameBuf::new(mk_buf(front_data), W, H);
let back_fb = FrameBuf::new(mk_buf(back_data), W, H);
Self {
back: back_fb,
front: Some(FrontState::Idle(front_fb)),
backend,
frame_count: 0,
}
}
}
impl<const W: usize, const H: usize, FB, B> SwapChain<W, H, FB, B>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
B: DisplayBackend<W, H, FB>,
{
pub fn get_back_buffer(&mut self) -> &mut FrameBuf<Rgb565, FB> {
&mut self.back
}
pub fn get_front_buffer(&self) -> Option<&FrameBuf<Rgb565, FB>> {
match &self.front {
Some(FrontState::Idle(fb)) => Some(fb),
_ => None,
}
}
pub fn present(&mut self) -> Result<(), DisplayError> {
self.present_impl(|backend, fb| backend.start_dma_transfer(fb))
}
pub fn try_present(&mut self) -> Result<(), DisplayError> {
if !self.is_ready() {
return Err(DisplayError::Busy);
}
self.present_impl(|backend, fb| backend.start_dma_transfer(fb))
}
pub fn present_region(&mut self, region: DisplayRegion) -> Result<(), DisplayError> {
self.present_impl(|backend, fb| backend.start_dma_transfer_region(fb, region))
}
pub fn try_present_region(&mut self, region: DisplayRegion) -> Result<(), DisplayError> {
if !self.is_ready() {
return Err(DisplayError::Busy);
}
self.present_impl(|backend, fb| backend.start_dma_transfer_region(fb, region))
}
pub fn wait_for_vsync(&mut self) {
if let Some(state) = self.front.take() {
let fb = state.recover();
self.front = Some(FrontState::Idle(fb));
}
}
pub fn is_ready(&self) -> bool {
self.front.as_ref().map_or(true, |s| s.is_ready())
}
pub fn frame_count(&self) -> u64 {
self.frame_count
}
pub fn reset_frame_count(&mut self) {
self.frame_count = 0;
}
pub fn dimensions(&self) -> (usize, usize) {
(W, H)
}
fn present_impl<F>(&mut self, start_fn: F) -> Result<(), DisplayError>
where
F: FnOnce(
&mut B,
FrameBuf<Rgb565, FB>,
) -> Result<B::Transfer, crate::display_backend::TransferError<FB>>,
{
let old_front = self
.front
.take()
.expect("SwapChain front buffer missing — double present?")
.recover();
let new_front = core::mem::replace(&mut self.back, old_front);
match start_fn(&mut self.backend, new_front) {
Ok(transfer) => {
self.front = Some(FrontState::InFlight(transfer));
self.frame_count += 1;
Ok(())
}
Err(e) => {
let recovered_front = core::mem::replace(&mut self.back, e.framebuffer);
self.front = Some(FrontState::Idle(recovered_front));
Err(e.error)
}
}
}
}
#[cfg(feature = "triple-buffering")]
pub struct TripleSwapChain<const W: usize, const H: usize, FB, B>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
B: DisplayBackend<W, H, FB>,
{
display: Option<FrontState<FrameBuf<Rgb565, FB>, B::Transfer>>,
ready: FrameBuf<Rgb565, FB>,
render: FrameBuf<Rgb565, FB>,
backend: B,
frame_count: u64,
}
#[cfg(feature = "triple-buffering")]
pub type StandardTripleSwapChain<const W: usize, const H: usize, B> =
TripleSwapChain<W, H, EndianCorrectedBuffer<'static, Rgb565>, B>;
#[cfg(feature = "triple-buffering")]
impl<const W: usize, const H: usize, B> StandardTripleSwapChain<W, H, B>
where
B: DisplayBackend<W, H, EndianCorrectedBuffer<'static, Rgb565>>,
{
pub fn from_static_slices(
display_data: &'static mut [Rgb565],
ready_data: &'static mut [Rgb565],
render_data: &'static mut [Rgb565],
big_endian: bool,
backend: B,
) -> Self {
let mk = |data: &'static mut [Rgb565]| {
let correction = if big_endian {
EndianCorrection::ToBigEndian
} else {
EndianCorrection::ToLittleEndian
};
FrameBuf::new(EndianCorrectedBuffer::new(data, correction), W, H)
};
Self {
display: Some(FrontState::Idle(mk(display_data))),
ready: mk(ready_data),
render: mk(render_data),
backend,
frame_count: 0,
}
}
}
#[cfg(feature = "triple-buffering")]
impl<const W: usize, const H: usize, FB, B> TripleSwapChain<W, H, FB, B>
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
B: DisplayBackend<W, H, FB>,
{
pub fn get_render_buffer(&mut self) -> &mut FrameBuf<Rgb565, FB> {
&mut self.render
}
pub fn present(&mut self) -> Result<(), DisplayError> {
self.present_impl(|backend, fb| backend.start_dma_transfer(fb))
}
pub fn try_present(&mut self) -> Result<(), DisplayError> {
let ready = self.display.as_ref().map_or(true, |s| s.is_ready());
if !ready {
return Err(DisplayError::Busy);
}
self.present_impl(|backend, fb| backend.start_dma_transfer(fb))
}
pub fn frame_count(&self) -> u64 {
self.frame_count
}
fn present_impl<F>(&mut self, start_fn: F) -> Result<(), DisplayError>
where
F: FnOnce(
&mut B,
FrameBuf<Rgb565, FB>,
) -> Result<B::Transfer, crate::display_backend::TransferError<FB>>,
{
let old_display = self
.display
.take()
.expect("TripleSwapChain display buffer missing")
.recover();
let rendered = core::mem::replace(&mut self.render, old_display);
match start_fn(&mut self.backend, rendered) {
Ok(transfer) => {
self.display = Some(FrontState::InFlight(transfer));
core::mem::swap(&mut self.ready, &mut self.render);
self.frame_count += 1;
Ok(())
}
Err(e) => {
let old_display = core::mem::replace(&mut self.render, e.framebuffer);
self.display = Some(FrontState::Idle(old_display));
Err(e.error)
}
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use crate::display_backend::{DmaTransfer, SimulatorBackend, TransferError};
use core::cell::Cell;
use embedded_graphics_core::pixelcolor::RgbColor;
use std::vec;
fn make_static_slice(n: usize) -> &'static mut [Rgb565] {
vec![Rgb565::BLACK; n].leak()
}
struct TrackingTransfer<FB: DMACapableFrameBufferBackend<Color = Rgb565>> {
framebuffer: Option<FrameBuf<Rgb565, FB>>,
}
impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> DmaTransfer for TrackingTransfer<FB> {
type Buffer = FrameBuf<Rgb565, FB>;
fn is_done(&self) -> bool {
true
}
fn wait(mut self) -> FrameBuf<Rgb565, FB> {
self.framebuffer.take().unwrap()
}
}
struct TrackingBackend {
region_present_count: Cell<usize>,
}
impl TrackingBackend {
fn new() -> Self {
Self {
region_present_count: Cell::new(0),
}
}
}
impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for TrackingBackend
where
FB: DMACapableFrameBufferBackend<Color = Rgb565>,
{
type Transfer = TrackingTransfer<FB>;
fn start_dma_transfer(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
) -> Result<TrackingTransfer<FB>, TransferError<FB>> {
Ok(TrackingTransfer {
framebuffer: Some(framebuffer),
})
}
fn start_dma_transfer_region(
&mut self,
framebuffer: FrameBuf<Rgb565, FB>,
_region: DisplayRegion,
) -> Result<TrackingTransfer<FB>, TransferError<FB>> {
self.region_present_count
.set(self.region_present_count.get() + 1);
Ok(TrackingTransfer {
framebuffer: Some(framebuffer),
})
}
}
fn make_swap_chain<B>(backend: B) -> StandardSwapChain<320, 240, B>
where
B: DisplayBackend<320, 240, EndianCorrectedBuffer<'static, Rgb565>>,
{
StandardSwapChain::<320, 240, _>::from_static_slices(
make_static_slice(320 * 240),
make_static_slice(320 * 240),
false,
backend,
)
}
#[test]
fn test_swapchain_creation() {
let sc = make_swap_chain(SimulatorBackend::new());
assert_eq!(sc.dimensions(), (320, 240));
assert_eq!(sc.frame_count(), 0);
assert!(sc.is_ready());
}
#[test]
fn test_swapchain_present() {
let mut sc = make_swap_chain(SimulatorBackend::new());
assert!(sc.present().is_ok());
assert_eq!(sc.frame_count(), 1);
}
#[test]
fn test_swapchain_multiple_presents() {
let mut sc = make_swap_chain(SimulatorBackend::new());
for _ in 0..5 {
assert!(sc.present().is_ok());
}
assert_eq!(sc.frame_count(), 5);
}
#[test]
fn test_swapchain_try_present() {
let mut sc = make_swap_chain(SimulatorBackend::new());
assert!(sc.try_present().is_ok());
assert_eq!(sc.frame_count(), 1);
}
#[test]
fn test_swapchain_frame_counter() {
let mut sc = make_swap_chain(SimulatorBackend::new());
assert_eq!(sc.frame_count(), 0);
sc.present().unwrap();
assert_eq!(sc.frame_count(), 1);
sc.present().unwrap();
assert_eq!(sc.frame_count(), 2);
sc.reset_frame_count();
assert_eq!(sc.frame_count(), 0);
}
#[test]
fn test_swapchain_get_back_buffer_always_available() {
let mut sc = make_swap_chain(SimulatorBackend::new());
sc.present().unwrap();
let _back = sc.get_back_buffer();
}
#[test]
fn test_swapchain_wait_for_vsync() {
let mut sc = make_swap_chain(SimulatorBackend::new());
sc.present().unwrap();
sc.wait_for_vsync();
assert!(sc.is_ready());
}
#[test]
fn test_swapchain_present_region() {
let fb0 = make_static_slice(64 * 64);
let fb1 = make_static_slice(64 * 64);
let mut sc = StandardSwapChain::<64, 64, _>::from_static_slices(
fb0,
fb1,
false,
TrackingBackend::new(),
);
sc.present_region(DisplayRegion::new(0, 0, 8, 8)).unwrap();
assert_eq!(sc.backend.region_present_count.get(), 1);
}
#[test]
fn test_swapchain_is_ready_after_simulator_present() {
let mut sc = make_swap_chain(SimulatorBackend::new());
sc.present().unwrap();
assert!(sc.is_ready());
}
#[cfg(feature = "triple-buffering")]
#[test]
fn test_triple_swapchain_present() {
let fb0 = make_static_slice(64 * 64);
let fb1 = make_static_slice(64 * 64);
let fb2 = make_static_slice(64 * 64);
let mut sc = StandardTripleSwapChain::<64, 64, _>::from_static_slices(
fb0,
fb1,
fb2,
false,
SimulatorBackend::new(),
);
assert_eq!(sc.frame_count(), 0);
sc.present().unwrap();
assert_eq!(sc.frame_count(), 1);
}
}