1use embedded_graphics_core::pixelcolor::Rgb565;
19use embedded_graphics_framebuf::{FrameBuf, backends::DMACapableFrameBufferBackend};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct DisplayRegion {
24 pub x: usize,
25 pub y: usize,
26 pub width: usize,
27 pub height: usize,
28}
29
30impl DisplayRegion {
31 pub const fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
32 Self {
33 x,
34 y,
35 width,
36 height,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum DisplayError {
44 Busy,
46 HardwareError,
48 InvalidBuffer,
50}
51
52pub struct TransferError<FB>
57where
58 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
59{
60 pub framebuffer: FrameBuf<Rgb565, FB>,
62 pub error: DisplayError,
64}
65
66impl<FB> core::fmt::Debug for TransferError<FB>
67where
68 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
69{
70 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71 f.debug_struct("TransferError")
72 .field("error", &self.error)
73 .finish_non_exhaustive()
74 }
75}
76
77pub trait DmaTransfer {
86 type Buffer;
88
89 fn is_done(&self) -> bool;
91
92 fn wait(self) -> Self::Buffer;
97}
98
99pub trait DisplayBackend<const W: usize, const H: usize, FB>
120where
121 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
122{
123 type Transfer: DmaTransfer<Buffer = FrameBuf<Rgb565, FB>>;
125
126 fn start_dma_transfer(
134 &mut self,
135 framebuffer: FrameBuf<Rgb565, FB>,
136 ) -> Result<Self::Transfer, TransferError<FB>>;
137
138 fn start_dma_transfer_region(
143 &mut self,
144 framebuffer: FrameBuf<Rgb565, FB>,
145 _region: DisplayRegion,
146 ) -> Result<Self::Transfer, TransferError<FB>> {
147 self.start_dma_transfer(framebuffer)
148 }
149}
150
151pub struct CompletedTransfer<FB>
158where
159 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
160{
161 framebuffer: Option<FrameBuf<Rgb565, FB>>,
162}
163
164impl<FB> DmaTransfer for CompletedTransfer<FB>
165where
166 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
167{
168 type Buffer = FrameBuf<Rgb565, FB>;
169
170 fn is_done(&self) -> bool {
171 true
172 }
173
174 fn wait(mut self) -> FrameBuf<Rgb565, FB> {
175 self.framebuffer
176 .take()
177 .expect("CompletedTransfer polled after completion")
178 }
179}
180
181pub struct SimulatorBackend;
189
190impl SimulatorBackend {
191 pub fn new() -> Self {
192 Self
193 }
194}
195
196impl Default for SimulatorBackend {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for SimulatorBackend
203where
204 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
205{
206 type Transfer = CompletedTransfer<FB>;
207
208 fn start_dma_transfer(
209 &mut self,
210 framebuffer: FrameBuf<Rgb565, FB>,
211 ) -> Result<CompletedTransfer<FB>, TransferError<FB>> {
212 Ok(CompletedTransfer {
213 framebuffer: Some(framebuffer),
214 })
215 }
216}
217
218#[cfg(test)]
221mod tests {
222 extern crate std;
223 use super::*;
224 use core::cell::Cell;
225 use embedded_graphics_core::pixelcolor::RgbColor;
226 use embedded_graphics_framebuf::backends::EndianCorrectedBuffer;
227 use std::vec;
228
229 type TestBackend = EndianCorrectedBuffer<'static, Rgb565>;
230
231 fn make_fb<const W: usize, const H: usize>() -> FrameBuf<Rgb565, TestBackend> {
232 use embedded_graphics_framebuf::backends::EndianCorrection;
233 let data: &'static mut [Rgb565] = vec![Rgb565::BLACK; W * H].leak();
234 FrameBuf::new(
235 EndianCorrectedBuffer::new(data, EndianCorrection::ToLittleEndian),
236 W,
237 H,
238 )
239 }
240
241 struct RegionTransfer<FB: DMACapableFrameBufferBackend<Color = Rgb565>> {
244 framebuffer: Option<FrameBuf<Rgb565, FB>>,
245 }
246
247 impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> DmaTransfer for RegionTransfer<FB> {
248 type Buffer = FrameBuf<Rgb565, FB>;
249 fn is_done(&self) -> bool {
250 true
251 }
252 fn wait(mut self) -> FrameBuf<Rgb565, FB> {
253 self.framebuffer.take().unwrap()
254 }
255 }
256
257 struct RegionTrackingBackend {
258 region_calls: Cell<usize>,
259 }
260
261 impl RegionTrackingBackend {
262 fn new() -> Self {
263 Self {
264 region_calls: Cell::new(0),
265 }
266 }
267 }
268
269 impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for RegionTrackingBackend
270 where
271 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
272 {
273 type Transfer = RegionTransfer<FB>;
274
275 fn start_dma_transfer(
276 &mut self,
277 framebuffer: FrameBuf<Rgb565, FB>,
278 ) -> Result<RegionTransfer<FB>, TransferError<FB>> {
279 Ok(RegionTransfer {
280 framebuffer: Some(framebuffer),
281 })
282 }
283
284 fn start_dma_transfer_region(
285 &mut self,
286 framebuffer: FrameBuf<Rgb565, FB>,
287 _region: DisplayRegion,
288 ) -> Result<RegionTransfer<FB>, TransferError<FB>> {
289 self.region_calls.set(self.region_calls.get() + 1);
290 Ok(RegionTransfer {
291 framebuffer: Some(framebuffer),
292 })
293 }
294 }
295
296 #[test]
299 fn test_simulator_backend_transfer_completes_immediately() {
300 let mut backend = SimulatorBackend::new();
301 let fb = make_fb::<2, 2>();
302 let xfer = <SimulatorBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
303 &mut backend,
304 fb,
305 )
306 .unwrap();
307 assert!(xfer.is_done());
308 let _fb = xfer.wait();
309 }
310
311 #[test]
312 fn test_simulator_backend_returns_buffer_on_wait() {
313 let mut backend = SimulatorBackend::new();
314 let fb = make_fb::<4, 4>();
315 let xfer = <SimulatorBackend as DisplayBackend<4, 4, TestBackend>>::start_dma_transfer(
316 &mut backend,
317 fb,
318 )
319 .unwrap();
320 let fb_back = xfer.wait();
322 assert_eq!(fb_back.width(), 4);
323 assert_eq!(fb_back.height(), 4);
324 }
325
326 #[test]
327 fn test_region_tracking_backend_counts_region_transfers() {
328 let mut backend = RegionTrackingBackend::new();
329 let fb = make_fb::<2, 2>();
330 let region = DisplayRegion::new(0, 0, 1, 1);
331 let xfer = <RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer_region(
332 &mut backend,
333 fb,
334 region,
335 )
336 .unwrap();
337 assert_eq!(backend.region_calls.get(), 1);
338 let _ = xfer.wait();
339 }
340
341 #[test]
342 fn test_full_transfer_does_not_increment_region_counter() {
343 let mut backend = RegionTrackingBackend::new();
344 let fb = make_fb::<2, 2>();
345 let xfer =
346 <RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
347 &mut backend,
348 fb,
349 )
350 .unwrap();
351 assert_eq!(backend.region_calls.get(), 0);
352 let _ = xfer.wait();
353 }
354}