From ca0bc90e851b82a71d904e1383a2844858f09b0a Mon Sep 17 00:00:00 2001
From: Savanni D'Gerinel <savanni@luminescent-dreams.com>
Date: Sun, 9 Mar 2025 11:31:28 -0400
Subject: [PATCH] Blit dirty data to the screen instead of everything

---
 pico-st7789/src/canvas.rs |   7 +++
 pico-st7789/src/main.rs   | 120 ++++++++++++++++++++++++++------------
 pico-st7789/src/st7789.rs |  44 +++++++++++++-
 3 files changed, 132 insertions(+), 39 deletions(-)

diff --git a/pico-st7789/src/canvas.rs b/pico-st7789/src/canvas.rs
index cb29b6b..052b443 100644
--- a/pico-st7789/src/canvas.rs
+++ b/pico-st7789/src/canvas.rs
@@ -8,6 +8,8 @@
 // e       c
 //   d d d
 
+use alloc::vec::Vec;
+
 use crate::font::{Font, Glyph};
 
 pub struct RGB {
@@ -18,6 +20,11 @@ pub struct RGB {
 
 pub trait Canvas {
     fn set_pixel(&mut self, x: usize, y: usize, color: &RGB);
+    fn buf(&self) -> &[u8];
+    fn partial(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> impl Iterator<Item = &[u8]>;
+    fn clean(&mut self);
+    fn dirty(&self) -> Option<(usize, usize, usize, usize)>;
+    fn width(&self) -> usize;
 
     fn fill(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
         for x in x1..x2 {
diff --git a/pico-st7789/src/main.rs b/pico-st7789/src/main.rs
index a2b3993..ad577c8 100644
--- a/pico-st7789/src/main.rs
+++ b/pico-st7789/src/main.rs
@@ -3,7 +3,10 @@
 
 extern crate alloc;
 
-use alloc::fmt::format;
+use alloc::{
+    fmt::format,
+    vec::{self, Vec},
+};
 use embedded_alloc::LlffHeap as Heap;
 use embedded_hal::{delay::DelayNs, digital::OutputPin};
 use fugit::RateExtU32;
@@ -48,6 +51,7 @@ static mut BUF: [u8; 163200] = [0; 163200];
 
 pub struct FrameBuf {
     pub buf: &'static mut [u8; 163200],
+    pub dirty: Option<(usize, usize, usize, usize)>,
     pub width: usize,
 }
 
@@ -55,6 +59,7 @@ impl FrameBuf {
     pub fn new() -> Self {
         Self {
             buf: unsafe { &mut BUF },
+            dirty: None,
             width: 170,
         }
     }
@@ -62,9 +67,51 @@ impl FrameBuf {
 
 impl Canvas for FrameBuf {
     fn set_pixel(&mut self, x: usize, y: usize, color: &RGB) {
-        self.buf[(y * self.width + x) * 3 + 0] = color.r << 2;
-        self.buf[(y * self.width + x) * 3 + 1] = color.g << 2;
-        self.buf[(y * self.width + x) * 3 + 2] = color.b << 2;
+        let addr = y * self.width + x;
+        self.buf[addr * 3 + 0] = color.r << 2;
+        self.buf[addr * 3 + 1] = color.g << 2;
+        self.buf[addr * 3 + 2] = color.b << 2;
+
+        if let Some((ref mut x1, ref mut y1, ref mut x2, ref mut y2)) = self.dirty {
+            if x < *x1 {
+                *x1 = x
+            }
+            if y < *y1 {
+                *y1 = y
+            }
+            if x > *x2 {
+                *x2 = x
+            }
+            if y > *y2 {
+                *y2 = y
+            }
+        } else {
+            self.dirty = Some((x, y, x, y));
+        }
+    }
+
+    fn buf(&self) -> &[u8] {
+        self.buf
+    }
+
+    fn partial(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> impl Iterator<Item = &[u8]> {
+        (y1..y2 + 1).map(move |y| {
+            let start = (y * self.width + x1) * 3;
+            let end = (y * self.width + x2) * 3;
+            &self.buf[start..end]
+        })
+    }
+
+    fn clean(&mut self) {
+        self.dirty = None;
+    }
+
+    fn dirty(&self) -> Option<(usize, usize, usize, usize)> {
+        self.dirty
+    }
+
+    fn width(&self) -> usize {
+        self.width
     }
 }
 
@@ -186,8 +233,8 @@ unsafe fn main() -> ! {
         Ok(v) if v == 0 => print(
             &mut canvas,
             &font_bitmap,
-            1,
-            1,
+            0,
+            0,
             &format(format_args!("POWER_DOWN")),
             &RGB {
                 r: 63,
@@ -199,8 +246,8 @@ unsafe fn main() -> ! {
         Err(err) => print(
             &mut canvas,
             &font_bitmap,
-            1,
-            1,
+            0,
+            0,
             &format(format_args!("ERROR: {:?}", err)),
             &RGB {
                 r: 63,
@@ -210,23 +257,34 @@ unsafe fn main() -> ! {
         ),
     }
 
+    {
+        let display = display.acquire();
+        display.blit_frame(&canvas);
+    }
+
     loop {
-        canvas.fill(0, 0, 170, 320, &RGB{ r: 0, g: 0, b: 0 });
-        print(&mut canvas, &font_bitmap, 1, 1, "A", &RGB{ r: 63, g: 63, b: 63 });
-        {
-            let display = display.acquire();
-            let _ = led.set_high();
-            timer.delay_ms(100);
-            display.send_buf(canvas.buf);
-            let _ = led.set_low();
+        canvas.fill(0, 0, 170, 60, &RGB { r: 0, g: 0, b: 0 });
+        if let Some((x1, y1, x2, y2)) = canvas.dirty() {
+            print(
+                &mut canvas,
+                &font_bitmap,
+                0,
+                0,
+                &format(format_args!("{} {} {} {}", x1, y1, x2, y2)),
+                &RGB {
+                    r: 63,
+                    g: 63,
+                    b: 63,
+                },
+            );
         }
 
         match light_sensor.read_full_spectrum() {
             Ok(full) => print(
                 &mut canvas,
                 &font_bitmap,
-                1,
-                10,
+                0,
+                20,
                 &format(format_args!("FULL: {}", full)),
                 &RGB {
                     r: 63,
@@ -237,8 +295,8 @@ unsafe fn main() -> ! {
             Err(err) => print(
                 &mut canvas,
                 &font_bitmap,
-                1,
-                10,
+                0,
+                20,
                 &format(format_args!("FULL: {:?}", err)),
                 &RGB {
                     r: 63,
@@ -247,21 +305,12 @@ unsafe fn main() -> ! {
                 },
             ),
         }
-        print(&mut canvas, &font_bitmap, 10, 1, "B", &RGB{ r: 63, g: 63, b: 63 });
-        {
-            let display = display.acquire();
-            let _ = led.set_high();
-            timer.delay_ms(100);
-            display.send_buf(canvas.buf);
-            let _ = led.set_low();
-        }
-
         match light_sensor.read_ir() {
             Ok(ir) => print(
                 &mut canvas,
                 &font_bitmap,
-                1,
-                20,
+                0,
+                40,
                 &format(format_args!("IR:   {}", ir)),
                 &RGB {
                     r: 63,
@@ -272,8 +321,8 @@ unsafe fn main() -> ! {
             Err(err) => print(
                 &mut canvas,
                 &font_bitmap,
-                1,
-                20,
+                0,
+                40,
                 &format(format_args!("IR:   {:?}", err)),
                 &RGB {
                     r: 63,
@@ -283,13 +332,10 @@ unsafe fn main() -> ! {
             ),
         }
 
-        print(&mut canvas, &font_bitmap, 20, 1, "C", &RGB{ r: 63, g: 63, b: 63 });
-
         {
             let display = display.acquire();
             let _ = led.set_high();
-            timer.delay_ms(100);
-            display.send_buf(canvas.buf);
+            display.blit_dirty(&canvas);
             let _ = led.set_low();
         }
 
diff --git a/pico-st7789/src/st7789.rs b/pico-st7789/src/st7789.rs
index c2bb0e2..449490c 100644
--- a/pico-st7789/src/st7789.rs
+++ b/pico-st7789/src/st7789.rs
@@ -5,6 +5,8 @@ use rp_pico::hal::{
     Spi, Timer,
 };
 
+use crate::canvas::Canvas;
+
 pub struct Step {
     param_cnt: usize,
     command: u8,
@@ -185,14 +187,52 @@ impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiP
         }
     }
 
-    pub fn send_buf(&mut self, frame: &[u8]) {
+    pub fn blit_frame(&mut self, frame: &impl Canvas) {
         // let _ = DISPOFF.send_command(&mut self.spi, &mut self.data_command);
         let _ = self.data_command.set_low();
         let _ = self.spi.write(&[RAMWR]);
         let _ = self.data_command.set_high();
-        let _ = self.spi.write(&frame);
+        let _ = self.spi.write(frame.buf());
         // let _ = DISPON.send_command(&mut self.spi, &mut self.data_command);
     }
+
+    pub fn blit_dirty(&mut self, frame: &impl Canvas) {
+        if let Some((start_x, start_y, end_x, end_y)) = frame.dirty() {
+            let end_y = end_y + 60;
+            Step {
+                param_cnt: 4,
+                command: 0x30,
+                params: [
+                    (start_y >> 8 & 0xff) as u8,
+                    (start_y & 0xff) as u8,
+                    (end_y >> 8 & 0xff) as u8,
+                    (end_y & 0xff) as u8,
+                ],
+                delay: None,
+            }
+            .send_command(&mut self.spi, &mut self.data_command);
+            Step {
+                param_cnt: 0,
+                command: 0x12,
+                params: [0, 0, 0, 0],
+                delay: None,
+            }
+            .send_command(&mut self.spi, &mut self.data_command);
+            let _ = self.data_command.set_low();
+            let _ = self.spi.write(&[RAMWR]);
+            let _ = self.data_command.set_high();
+            for row in frame.partial(0, start_y, frame.width(), end_y) {
+                let _ = self.spi.write(row);
+            }
+            Step {
+                param_cnt: 0,
+                command: 0x13,
+                params: [0, 0, 0, 0],
+                delay: None,
+            }
+            .send_command(&mut self.spi, &mut self.data_command);
+        }
+    }
 }
 
 impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>> Drop