embassy_agb/
display.rs

1//! Async display operations with VBlank support
2//!
3//! VBlank occurs ~60 times/sec between scanlines 160-227, providing a safe window
4//! for graphics updates without tearing.
5//!
6//! ## Registers
7//! - `DISPSTAT` (0x4000004): bit 3 enables VBlank IRQ
8//! - `IE` (0x4000200): bit 0 for VBlank
9//! - `IF` (0x4000202): bit 0 to acknowledge
10
11use core::cell::Cell;
12use core::future::Future;
13use core::pin::Pin;
14use core::task::{Context, Poll};
15use portable_atomic::{AtomicBool, AtomicUsize, Ordering};
16
17use agb::display::GraphicsDist;
18use agb::interrupt::{add_interrupt_handler, Interrupt, VBlank};
19use embassy_sync::waitqueue::AtomicWaker;
20
21/// VBlank counter
22static VBLANK_COUNTER: AtomicUsize = AtomicUsize::new(0);
23
24/// VBlank waker  
25static VBLANK_WAKER: AtomicWaker = AtomicWaker::new();
26
27/// Whether the VBlank handler is initialized
28static VBLANK_INITIALIZED: AtomicBool = AtomicBool::new(false);
29
30/// Initialize VBlank interrupt handler
31fn init_embassy_vblank() {
32    if VBLANK_INITIALIZED.swap(true, Ordering::SeqCst) {
33        return; // Already initialized
34    }
35
36    let handler = unsafe {
37        add_interrupt_handler(Interrupt::VBlank, |_| {
38            VBLANK_COUNTER.store(VBLANK_COUNTER.load(Ordering::SeqCst) + 1, Ordering::SeqCst);
39            VBLANK_WAKER.wake();
40        })
41    };
42    core::mem::forget(handler);
43}
44
45/// Async wrapper for agb display operations
46pub struct AsyncDisplay<'a> {
47    graphics: agb::display::Graphics<'a>,
48    #[allow(dead_code)]
49    vblank: VBlank,
50}
51
52impl<'a> AsyncDisplay<'a> {
53    pub(crate) fn new(graphics_dist: &'a mut GraphicsDist) -> Self {
54        init_embassy_vblank();
55
56        Self {
57            graphics: graphics_dist.get(),
58            vblank: VBlank::get(),
59        }
60    }
61
62    /// Wait for the next VBlank (~16.7ms at 60Hz)
63    pub async fn wait_for_vblank(&self) {
64        EmbassyVBlankFuture::new().await
65    }
66
67    /// Get a frame for rendering, waiting for VBlank if needed
68    pub async fn frame(&mut self) -> agb::display::GraphicsFrame<'_> {
69        self.wait_for_vblank().await;
70        self.graphics.frame()
71    }
72
73    /// Get a frame for rendering without waiting for VBlank
74    /// Use this when you've already called wait_for_vblank() separately
75    pub fn frame_no_wait(&mut self) -> agb::display::GraphicsFrame<'_> {
76        self.graphics.frame()
77    }
78
79    /// Get access to the underlying graphics for synchronous operations
80    pub fn graphics(&mut self) -> &mut agb::display::Graphics<'a> {
81        &mut self.graphics
82    }
83}
84
85/// Future that completes on next VBlank
86struct EmbassyVBlankFuture {
87    last_count: Cell<usize>,
88}
89
90impl EmbassyVBlankFuture {
91    fn new() -> Self {
92        Self {
93            last_count: Cell::new(VBLANK_COUNTER.load(Ordering::SeqCst)),
94        }
95    }
96}
97
98impl Future for EmbassyVBlankFuture {
99    type Output = ();
100
101    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
102        let current_count = VBLANK_COUNTER.load(Ordering::SeqCst);
103        let last_count = self.last_count.get();
104
105        if current_count > last_count {
106            // VBlank occurred since last check
107            self.last_count.set(current_count);
108            Poll::Ready(())
109        } else {
110            // Register waker for next VBlank
111            VBLANK_WAKER.register(cx.waker());
112
113            // Check again in case VBlank occurred between the first check and waker registration
114            let current_count = VBLANK_COUNTER.load(Ordering::SeqCst);
115            if current_count > last_count {
116                self.last_count.set(current_count);
117                Poll::Ready(())
118            } else {
119                Poll::Pending
120            }
121        }
122    }
123}
124
125/// Future for DMA-based transfers (placeholder for future implementation)
126pub struct DmaTransferFuture {
127    _phantom: core::marker::PhantomData<()>,
128}
129
130impl Future for DmaTransferFuture {
131    type Output = ();
132
133    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
134        // For now, complete immediately
135        // TODO: Implement actual DMA async support
136        Poll::Ready(())
137    }
138}