Skip to main content

shuyi_playdate/
display.rs

1//! The frame on the Playdate's screen: a game's frame drawn with its
2//! one-bit look into a frame of our own (`shuyi_ui::frame::onebit`, redrawing
3//! only where it differs from the last), then the rows that differ from the
4//! SDK's frame buffer copied into it and marked (`markUpdatedRows`). The
5//! loading card and the errors are the SDK's text, in the system's font.
6
7use alloc::borrow::ToOwned;
8use alloc::boxed::Box;
9use alloc::string::String;
10
11use shuyi_platform::{Card, Frame, OneBitLook, Show};
12use shuyi_ui::frame::Rgb;
13use shuyi_ui::frame::glyph_buffer::GlyphBuffer;
14use shuyi_ui::frame::onebit::{self, FRAME_BYTES, ROW_BYTES, RowMask, luminance};
15
16use crate::report::{Measured, Phases};
17use crate::sdk::Sdk;
18
19/// What a one-bit look that isn't there says.
20// check-text: not player text (a build problem, shown as errors are)
21const NO_LOOK: &str = "This game has no one-bit look: its Look has no one_bit part.";
22
23/// Whether `rgb` is nearer white than black.
24fn is_light(rgb: Rgb) -> bool {
25    luminance(rgb) >= 128
26}
27
28/// The first and last row of each run of neighbouring rows of `rows`.
29fn runs(rows: &RowMask) -> impl Iterator<Item = (i32, i32)> + '_ {
30    let mut rows = rows.rows().peekable();
31    core::iter::from_fn(move || {
32        let first = rows.next()?;
33        let mut last = first;
34        while rows.next_if_eq(&(last + 1)).is_some() {
35            last += 1;
36        }
37        Some((first, last))
38    })
39}
40
41/// What the screen last showed.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub(crate) enum Showing {
44    /// Nothing yet.
45    #[default]
46    Nothing,
47    /// The loading card.
48    Card,
49    /// The errors, or a game without a one-bit look.
50    Errors,
51    /// The game's frame.
52    Game,
53}
54
55/// The screen, as the backend last drew it.
56#[derive(Debug)]
57pub struct Screen {
58    /// The frame drawn with [`shown`](Self::shown), laid out as the SDK's.
59    drawn: Box<onebit::Frame>,
60    /// The game's frame last drawn, `None` before the first and after text,
61    /// which is what the SDK's frame buffer then holds.
62    shown: Option<GlyphBuffer>,
63    /// The text last drawn, `None` when the last thing drawn was the game.
64    text: Option<String>,
65    /// Whether the SDK's frame buffer may differ from [`drawn`](Self::drawn)
66    /// though the game's frame didn't change: a system menu's been over it.
67    stale: bool,
68    /// Whether anything reached the SDK since [`take_changed`](Self::take_changed).
69    changed: bool,
70    showing: Showing,
71    /// What the run report measures of the draws, while it is on.
72    measured: Option<Measured>,
73}
74
75impl Default for Screen {
76    fn default() -> Self {
77        Self {
78            drawn: Box::new([u8::MAX; FRAME_BYTES]),
79            shown: None,
80            text: None,
81            stale: true,
82            changed: false,
83            showing: Showing::Nothing,
84            measured: None,
85        }
86    }
87}
88
89impl Screen {
90    /// Shows `frame`.
91    pub fn present(&mut self, sdk: &impl Sdk, frame: &Frame<'_>) {
92        self.show(sdk, frame);
93    }
94
95    /// [`present`](Self::present), with the SDK's type named for its timer.
96    fn show<S: Sdk>(&mut self, sdk: &S, frame: &Frame<'_>) {
97        match frame.show {
98            Show::Card(Card::Text {
99                line, background, ..
100            }) => self.text(sdk, line, is_light(*background), Showing::Card),
101            Show::Card(card) => self.text(sdk, "", is_light(card.background()), Showing::Card),
102            Show::Errors(text) => self.text(sdk, text, true, Showing::Errors),
103            Show::Game { buffer, look, .. } => match look.and_then(|look| look.one_bit) {
104                Some(one_bit) => self.game(sdk, buffer, &one_bit),
105                None => self.text(sdk, NO_LOOK, true, Showing::Errors),
106            },
107            _ => {}
108        }
109    }
110
111    /// What the screen last showed.
112    pub(crate) fn showing(&self) -> Showing {
113        self.showing
114    }
115
116    /// Times each draw of the game's frame from now on, and each of its
117    /// phases if `phases`.
118    pub(crate) fn measure(&mut self, phases: bool) {
119        self.measured = Some(Measured {
120            phases: phases.then(Phases::default),
121            ..Measured::default()
122        });
123    }
124
125    /// What was measured since the last take, which starts again; nothing
126    /// unless [`measure`](Self::measure) was called.
127    pub(crate) fn take_measured(&mut self) -> Measured {
128        let Some(measured) = &mut self.measured else {
129            return Measured::default();
130        };
131        let phases = measured.phases.map(|_| Phases::default());
132        core::mem::replace(
133            measured,
134            Measured {
135                phases,
136                ..Measured::default()
137            },
138        )
139    }
140
141    /// The next frame must reach the SDK's frame buffer whatever it shows:
142    /// something else drew over it.
143    pub fn invalidate(&mut self) {
144        self.stale = true;
145    }
146
147    /// Whether anything reached the SDK since the last call: the update
148    /// callback returns it.
149    pub fn take_changed(&mut self) -> bool {
150        core::mem::take(&mut self.changed)
151    }
152
153    fn text(&mut self, sdk: &impl Sdk, text: &str, white: bool, showing: Showing) {
154        self.showing = showing;
155        if self.text.as_deref() == Some(text) && !self.stale {
156            return;
157        }
158        sdk.draw_text(text, white);
159        self.text = Some(text.to_owned());
160        self.shown = None;
161        self.stale = false;
162        self.changed = true;
163    }
164
165    fn game<S: Sdk>(&mut self, sdk: &S, buffer: &GlyphBuffer, look: &OneBitLook<'_>) {
166        self.text = None;
167        self.showing = Showing::Game;
168        if self.stale || self.shown.as_ref() != Some(buffer) {
169            let start = self.measured.is_some().then(S::clock_us);
170            self.draw::<S>(buffer, look);
171            self.copy(sdk);
172            if let (Some(start), Some(measured)) = (start, &mut self.measured) {
173                measured.draw.add(S::clock_us().saturating_sub(start));
174            }
175        }
176        self.stale = false;
177    }
178
179    /// Draws `buffer` into [`drawn`](Self::drawn): where it differs from the
180    /// last frame, or whole with each phase timed when phases are measured.
181    fn draw<S: Sdk>(&mut self, buffer: &GlyphBuffer, look: &OneBitLook<'_>) {
182        let (font, map) = (look.font, look.map);
183        if let Some(phases) = self.measured.as_mut().and_then(|m| m.phases.as_mut()) {
184            let mut last = S::clock_us();
185            let mark = &mut |phase| {
186                let now = S::clock_us();
187                phases.add(phase, now.saturating_sub(last));
188                last = now;
189            };
190            onebit::render_marked_with(buffer, font, &look.images, map, &mut self.drawn, mark);
191            self.shown = Some(buffer.clone());
192            return;
193        }
194        if let Some(shown) = &mut self.shown {
195            onebit::render_changed_with(shown, buffer, font, &look.images, map, &mut self.drawn);
196            shown.clone_from(buffer);
197        } else {
198            onebit::render_with(buffer, font, &look.images, map, &mut self.drawn);
199            self.shown = Some(buffer.clone());
200        }
201    }
202
203    /// Copies the rows that differ into the SDK's frame buffer, and marks
204    /// each run of them.
205    fn copy(&mut self, sdk: &impl Sdk) {
206        let mut changed = RowMask::default();
207        let drawn = &self.drawn;
208        sdk.with_frame(|screen| {
209            changed = onebit::changed_rows(screen, drawn);
210            for y in changed.rows() {
211                let start = usize::try_from(y).unwrap_or(0) * ROW_BYTES;
212                let row = start..start + ROW_BYTES;
213                screen[row.clone()].copy_from_slice(&drawn[row]);
214            }
215        });
216        for (first, last) in runs(&changed) {
217            sdk.mark_rows(first, last);
218        }
219        self.changed |= !changed.is_empty();
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use alloc::vec;
226    use alloc::vec::Vec;
227
228    use super::*;
229
230    fn mask(rows: &[i32]) -> RowMask {
231        let mut mask = RowMask::default();
232        for &y in rows {
233            mask.insert(y);
234        }
235        mask
236    }
237
238    #[test]
239    fn neighbouring_rows_are_one_run() {
240        let got: Vec<_> = runs(&mask(&[3, 4, 5, 9, 239])).collect();
241        assert_eq!(got, vec![(3, 5), (9, 9), (239, 239)]);
242        assert_eq!(runs(&mask(&[])).count(), 0);
243    }
244}