st77916 0.1.1

A Rust driver for the ST77916 TFT-LCD display controller
Documentation
//! Framebuffer management, dirty-band tracking, and buffer marker types.

extern crate alloc;

use alloc::boxed::Box;
use core::marker::PhantomData;

use super::St77916Color;
use crate::{ControllerInterface, DriverError, DriverResult, ResetInterface, St77916};

// ---------------------------------------------------------------------------
// Framebuffer
// ---------------------------------------------------------------------------

/// Holds either a static or heap-allocated framebuffer.
pub enum Framebuffer {
    Static(&'static mut [u8]),
    Heap(Box<[u8]>),
}

impl Framebuffer {
    /// Allocate a zeroed heap framebuffer of `N` bytes.
    pub fn heap<const N: usize>() -> Self {
        Framebuffer::Heap(Box::new([0u8; N]))
    }

    pub fn as_mut_slice(&mut self) -> &mut [u8] {
        match self {
            Framebuffer::Static(arr) => arr,
            Framebuffer::Heap(boxed) => boxed,
        }
    }

    pub fn as_slice(&self) -> &[u8] {
        match self {
            Framebuffer::Static(arr) => arr,
            Framebuffer::Heap(boxed) => boxed,
        }
    }

    pub fn len(&self) -> usize {
        self.as_slice().len()
    }

    pub fn is_empty(&self) -> bool {
        self.as_slice().is_empty()
    }
}

impl core::ops::Deref for Framebuffer {
    type Target = [u8];
    fn deref(&self) -> &Self::Target {
        self.as_slice()
    }
}

impl core::ops::DerefMut for Framebuffer {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.as_mut_slice()
    }
}

/// Compute framebuffer size in bytes for a given display and color mode.
pub const fn framebuffer_size(
    display: crate::DisplaySize,
    color: crate::ColorMode,
) -> usize {
    (display.width as usize) * (display.height as usize) * color.bytes_per_pixel()
}

// ---------------------------------------------------------------------------
// Dirty band tracker
// ---------------------------------------------------------------------------

/// Tracks the dirty vertical band of the framebuffer.
///
/// When `y_min > y_max` the region is clean (nothing to flush).
/// Draw operations expand the band; [`flush`](St77916::flush) resets it.
#[derive(Debug, Clone, Copy)]
pub(crate) struct DirtyBand {
    pub(crate) y_min: u16,
    pub(crate) y_max: u16,
}

impl DirtyBand {
    pub fn clean() -> Self {
        Self {
            y_min: u16::MAX,
            y_max: 0,
        }
    }

    #[inline]
    pub fn is_clean(&self) -> bool {
        self.y_min > self.y_max
    }

    #[inline]
    pub fn mark(&mut self, y_start: u16, y_end: u16) {
        self.y_min = self.y_min.min(y_start);
        self.y_max = self.y_max.max(y_end);
    }

    #[inline]
    pub fn mark_all(&mut self, height: u16) {
        self.y_min = 0;
        self.y_max = height - 1;
    }

    #[inline]
    pub fn reset(&mut self) {
        *self = Self::clean();
    }
}

// ---------------------------------------------------------------------------
// Buffer marker types
// ---------------------------------------------------------------------------

/// Single-buffered mode with dirty-band tracking.
///
/// All `DrawTarget` operations write to the internal framebuffer and
/// automatically track which rows were modified.
/// [`flush()`](St77916::flush) sends only the dirty band.
pub struct Buffered<COLOR: St77916Color = embedded_graphics_core::pixelcolor::Rgb565> {
    pub(crate) data: Framebuffer,
    pub(crate) dirty: DirtyBand,
    pub(crate) _color: PhantomData<COLOR>,
}

/// Double-buffered mode for DMA overlap.
///
/// Drawing goes to the back buffer; [`swap_buffers()`](St77916::swap_buffers)
/// exchanges front and back, then [`flush_front()`](St77916::flush_front)
/// sends the completed frame.
pub struct DoubleBuffered<COLOR: St77916Color = embedded_graphics_core::pixelcolor::Rgb565> {
    pub(crate) bufs: [Framebuffer; 2],
    pub(crate) back_idx: usize,
    pub(crate) _color: PhantomData<COLOR>,
}

impl<COLOR: St77916Color> DoubleBuffered<COLOR> {
    pub(crate) fn back(&self) -> &Framebuffer {
        &self.bufs[self.back_idx]
    }

    pub(crate) fn back_mut(&mut self) -> &mut Framebuffer {
        &mut self.bufs[self.back_idx]
    }

    pub(crate) fn front(&self) -> &Framebuffer {
        &self.bufs[self.back_idx ^ 1]
    }
}

/// Unbuffered `DrawTarget` — each draw sends pixels directly to the display.
///
/// Zero RAM cost, but every draw call is a hardware transaction.
pub struct Unbuffered<COLOR: St77916Color = embedded_graphics_core::pixelcolor::Rgb565> {
    pub(crate) _color: PhantomData<COLOR>,
}

// ---------------------------------------------------------------------------
// Buffered driver methods
// ---------------------------------------------------------------------------

impl<IFACE, RST, COLOR: St77916Color> St77916<IFACE, RST, Buffered<COLOR>>
where
    IFACE: ControllerInterface,
    RST: ResetInterface,
{
    /// Flush only the dirty region of the framebuffer to the display.
    ///
    /// If nothing has been drawn since the last flush, this is a no-op.
    pub fn flush(&mut self) -> DriverResult<(), IFACE, RST> {
        if self.buffer.dirty.is_clean() {
            return Ok(());
        }

        let y_min = self.buffer.dirty.y_min;
        let y_max = self.buffer.dirty.y_max;
        let bpp = COLOR::BYTES_PER_PIXEL;
        let stride = self.config.width as usize * bpp;

        self.set_window(0, y_min, self.config.width - 1, y_max)?;

        let start = y_min as usize * stride;
        let end = (y_max as usize + 1) * stride;
        self.interface
            .send_pixels(&self.buffer.data[start..end])
            .map_err(DriverError::InterfaceError)?;

        self.buffer.dirty.reset();
        Ok(())
    }

    /// Flush the entire framebuffer, ignoring the dirty region.
    pub fn full_flush(&mut self) -> DriverResult<(), IFACE, RST> {
        self.set_window(0, 0, self.config.width - 1, self.config.height - 1)?;
        self.interface
            .send_pixels(&self.buffer.data)
            .map_err(DriverError::InterfaceError)?;
        self.buffer.dirty.reset();
        Ok(())
    }

    /// Get the framebuffer as a mutable byte slice.
    pub fn framebuffer_mut(&mut self) -> &mut [u8] {
        self.buffer.data.as_mut_slice()
    }

    /// Get the framebuffer as an immutable byte slice.
    pub fn framebuffer(&self) -> &[u8] {
        self.buffer.data.as_slice()
    }

    /// Mark a vertical band as dirty.
    pub fn mark_dirty(&mut self, y_start: u16, y_end: u16) {
        self.buffer.dirty.mark(y_start, y_end);
    }

    /// Mark the entire framebuffer as dirty.
    pub fn mark_all_dirty(&mut self) {
        self.buffer.dirty.mark_all(self.config.height);
    }

    /// Returns `true` if no draw operations have occurred since the last flush.
    pub fn is_clean(&self) -> bool {
        self.buffer.dirty.is_clean()
    }

    /// Flush a rectangular sub-region of the framebuffer.
    pub fn partial_flush(
        &mut self,
        x_start: u16,
        x_end: u16,
        y_start: u16,
        y_end: u16,
    ) -> DriverResult<(), IFACE, RST> {
        self.set_window(x_start, y_start, x_end, y_end)?;
        let bpp = COLOR::BYTES_PER_PIXEL;
        let fb_stride = self.config.width as usize * bpp;
        let row_width = (x_end - x_start + 1) as usize * bpp;

        for y in y_start..=y_end {
            let offset = y as usize * fb_stride + x_start as usize * bpp;
            let row_end = offset + row_width;
            if row_end <= self.buffer.data.len() {
                self.interface
                    .send_pixels(&self.buffer.data[offset..row_end])
                    .map_err(DriverError::InterfaceError)?;
            } else {
                return Err(DriverError::InvalidConfiguration(
                    "Framebuffer slice out of bounds",
                ));
            }
        }
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// Double-buffered driver methods
// ---------------------------------------------------------------------------

impl<IFACE, RST, COLOR: St77916Color> St77916<IFACE, RST, DoubleBuffered<COLOR>>
where
    IFACE: ControllerInterface,
    RST: ResetInterface,
{
    /// Swap front and back buffers.
    pub fn swap_buffers(&mut self) {
        self.buffer.back_idx ^= 1;
    }

    /// Flush the front buffer to the display.
    pub fn flush_front(&mut self) -> DriverResult<(), IFACE, RST> {
        self.set_window(0, 0, self.config.width - 1, self.config.height - 1)?;
        self.interface
            .send_pixels(self.buffer.front())
            .map_err(DriverError::InterfaceError)
    }

    /// Flush the back buffer to the display.
    pub fn flush(&mut self) -> DriverResult<(), IFACE, RST> {
        self.set_window(0, 0, self.config.width - 1, self.config.height - 1)?;
        self.interface
            .send_pixels(self.buffer.back())
            .map_err(DriverError::InterfaceError)
    }

    /// Get the back buffer as a mutable byte slice.
    pub fn back_buffer_mut(&mut self) -> &mut [u8] {
        self.buffer.back_mut().as_mut_slice()
    }

    /// Get the front buffer as an immutable byte slice.
    pub fn front_buffer(&self) -> &[u8] {
        self.buffer.front().as_slice()
    }
}