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 AsyncDmaTransfer: DmaTransfer {
104 type WaitFuture: core::future::Future<Output = Self::Buffer>;
106
107 fn wait_async(self) -> Self::WaitFuture;
109}
110
111pub trait DisplayBackend<const W: usize, const H: usize, FB>
132where
133 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
134{
135 type Transfer: DmaTransfer<Buffer = FrameBuf<Rgb565, FB>>;
137
138 fn start_dma_transfer(
146 &mut self,
147 framebuffer: FrameBuf<Rgb565, FB>,
148 ) -> Result<Self::Transfer, TransferError<FB>>;
149
150 fn start_dma_transfer_region(
155 &mut self,
156 framebuffer: FrameBuf<Rgb565, FB>,
157 _region: DisplayRegion,
158 ) -> Result<Self::Transfer, TransferError<FB>> {
159 self.start_dma_transfer(framebuffer)
160 }
161
162 #[cfg(feature = "dma2d")]
166 fn hardware_clear_depth(&mut self, _zbuffer: &mut [crate::ZDepth]) -> bool {
167 false
168 }
169}
170
171pub struct CompletedTransfer<FB>
178where
179 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
180{
181 framebuffer: Option<FrameBuf<Rgb565, FB>>,
182}
183
184impl<FB> DmaTransfer for CompletedTransfer<FB>
185where
186 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
187{
188 type Buffer = FrameBuf<Rgb565, FB>;
189
190 fn is_done(&self) -> bool {
191 true
192 }
193
194 fn wait(mut self) -> FrameBuf<Rgb565, FB> {
195 self.framebuffer
196 .take()
197 .expect("CompletedTransfer polled after completion")
198 }
199}
200
201impl<FB> core::future::Future for CompletedTransfer<FB>
202where
203 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
204{
205 type Output = FrameBuf<Rgb565, FB>;
206
207 fn poll(
208 self: core::pin::Pin<&mut Self>,
209 _cx: &mut core::task::Context<'_>,
210 ) -> core::task::Poll<Self::Output> {
211 let buf = unsafe { self.get_unchecked_mut() }
212 .framebuffer
213 .take()
214 .expect("CompletedTransfer polled after completion");
215 core::task::Poll::Ready(buf)
216 }
217}
218
219impl<FB> AsyncDmaTransfer for CompletedTransfer<FB>
220where
221 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
222{
223 type WaitFuture = Self;
224
225 fn wait_async(self) -> Self::WaitFuture {
226 self
227 }
228}
229
230pub struct SimulatorBackend;
238
239impl SimulatorBackend {
240 pub fn new() -> Self {
241 Self
242 }
243}
244
245impl Default for SimulatorBackend {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for SimulatorBackend
252where
253 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
254{
255 type Transfer = CompletedTransfer<FB>;
256
257 fn start_dma_transfer(
258 &mut self,
259 framebuffer: FrameBuf<Rgb565, FB>,
260 ) -> Result<CompletedTransfer<FB>, TransferError<FB>> {
261 Ok(CompletedTransfer {
262 framebuffer: Some(framebuffer),
263 })
264 }
265}
266
267#[cfg(test)]
270mod tests {
271 extern crate std;
272 use super::*;
273 use core::cell::Cell;
274 use embedded_graphics_core::pixelcolor::RgbColor;
275 use embedded_graphics_framebuf::backends::EndianCorrectedBuffer;
276 use std::vec;
277
278 type TestBackend = EndianCorrectedBuffer<'static, Rgb565>;
279
280 fn make_fb<const W: usize, const H: usize>() -> FrameBuf<Rgb565, TestBackend> {
281 use embedded_graphics_framebuf::backends::EndianCorrection;
282 let data: &'static mut [Rgb565] = vec![Rgb565::BLACK; W * H].leak();
283 FrameBuf::new(
284 EndianCorrectedBuffer::new(data, EndianCorrection::ToLittleEndian),
285 W,
286 H,
287 )
288 }
289
290 struct RegionTransfer<FB: DMACapableFrameBufferBackend<Color = Rgb565>> {
293 framebuffer: Option<FrameBuf<Rgb565, FB>>,
294 }
295
296 impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> DmaTransfer for RegionTransfer<FB> {
297 type Buffer = FrameBuf<Rgb565, FB>;
298 fn is_done(&self) -> bool {
299 true
300 }
301 fn wait(mut self) -> FrameBuf<Rgb565, FB> {
302 self.framebuffer.take().unwrap()
303 }
304 }
305
306 impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> core::future::Future for RegionTransfer<FB> {
307 type Output = FrameBuf<Rgb565, FB>;
308 fn poll(
309 self: core::pin::Pin<&mut Self>,
310 _cx: &mut core::task::Context<'_>,
311 ) -> core::task::Poll<Self::Output> {
312 core::task::Poll::Ready(
313 unsafe { self.get_unchecked_mut() }
314 .framebuffer
315 .take()
316 .unwrap(),
317 )
318 }
319 }
320
321 impl<FB: DMACapableFrameBufferBackend<Color = Rgb565>> AsyncDmaTransfer for RegionTransfer<FB> {
322 type WaitFuture = Self;
323 fn wait_async(self) -> Self::WaitFuture {
324 self
325 }
326 }
327
328 struct RegionTrackingBackend {
329 region_calls: Cell<usize>,
330 }
331
332 impl RegionTrackingBackend {
333 fn new() -> Self {
334 Self {
335 region_calls: Cell::new(0),
336 }
337 }
338 }
339
340 impl<const W: usize, const H: usize, FB> DisplayBackend<W, H, FB> for RegionTrackingBackend
341 where
342 FB: DMACapableFrameBufferBackend<Color = Rgb565>,
343 {
344 type Transfer = RegionTransfer<FB>;
345
346 fn start_dma_transfer(
347 &mut self,
348 framebuffer: FrameBuf<Rgb565, FB>,
349 ) -> Result<RegionTransfer<FB>, TransferError<FB>> {
350 Ok(RegionTransfer {
351 framebuffer: Some(framebuffer),
352 })
353 }
354
355 fn start_dma_transfer_region(
356 &mut self,
357 framebuffer: FrameBuf<Rgb565, FB>,
358 _region: DisplayRegion,
359 ) -> Result<RegionTransfer<FB>, TransferError<FB>> {
360 self.region_calls.set(self.region_calls.get() + 1);
361 Ok(RegionTransfer {
362 framebuffer: Some(framebuffer),
363 })
364 }
365 }
366
367 #[test]
370 fn test_simulator_backend_transfer_completes_immediately() {
371 let mut backend = SimulatorBackend::new();
372 let fb = make_fb::<2, 2>();
373 let xfer = <SimulatorBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
374 &mut backend,
375 fb,
376 )
377 .unwrap();
378 assert!(xfer.is_done());
379 let _fb = xfer.wait();
380 }
381
382 #[test]
383 fn test_simulator_backend_returns_buffer_on_wait() {
384 let mut backend = SimulatorBackend::new();
385 let fb = make_fb::<4, 4>();
386 let xfer = <SimulatorBackend as DisplayBackend<4, 4, TestBackend>>::start_dma_transfer(
387 &mut backend,
388 fb,
389 )
390 .unwrap();
391 let fb_back = xfer.wait();
393 assert_eq!(fb_back.width(), 4);
394 assert_eq!(fb_back.height(), 4);
395 }
396
397 #[test]
398 fn test_region_tracking_backend_counts_region_transfers() {
399 let mut backend = RegionTrackingBackend::new();
400 let fb = make_fb::<2, 2>();
401 let region = DisplayRegion::new(0, 0, 1, 1);
402 let xfer = <RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer_region(
403 &mut backend,
404 fb,
405 region,
406 )
407 .unwrap();
408 assert_eq!(backend.region_calls.get(), 1);
409 let _ = xfer.wait();
410 }
411
412 #[test]
413 fn test_full_transfer_does_not_increment_region_counter() {
414 let mut backend = RegionTrackingBackend::new();
415 let fb = make_fb::<2, 2>();
416 let xfer =
417 <RegionTrackingBackend as DisplayBackend<2, 2, TestBackend>>::start_dma_transfer(
418 &mut backend,
419 fb,
420 )
421 .unwrap();
422 assert_eq!(backend.region_calls.get(), 0);
423 let _ = xfer.wait();
424 }
425}