atomic fb
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -1264,6 +1264,7 @@ dependencies = [
|
|||||||
"embassy-usb",
|
"embassy-usb",
|
||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
"embedded-hal 0.2.7",
|
"embedded-hal 0.2.7",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
"embedded-hal-async",
|
"embedded-hal-async",
|
||||||
"embedded-hal-bus",
|
"embedded-hal-bus",
|
||||||
"embedded-layout",
|
"embedded-layout",
|
||||||
@@ -1991,7 +1992,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "st7365p-lcd"
|
name = "st7365p-lcd"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=2a484aaab5f6b9824cc813fe4ae087250c9e39c1#2a484aaab5f6b9824cc813fe4ae087250c9e39c1"
|
source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=1d15123929fa7ef73d5d6aead7faf1bba50ce915#1d15123929fa7ef73d5d6aead7faf1bba50ce915"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"embedded-graphics-core",
|
"embedded-graphics-core",
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ cyw43-pio = { version = "0.3.0", optional = true }
|
|||||||
|
|
||||||
embedded-hal-bus = { version = "0.3.0", features = ["async"] }
|
embedded-hal-bus = { version = "0.3.0", features = ["async"] }
|
||||||
embedded-hal = "0.2.7"
|
embedded-hal = "0.2.7"
|
||||||
|
embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" }
|
||||||
embedded-hal-async = "1.0.0"
|
embedded-hal-async = "1.0.0"
|
||||||
cortex-m = { version = "0.7.7" }
|
cortex-m = { version = "0.7.7" }
|
||||||
cortex-m-rt = "0.7.5"
|
cortex-m-rt = "0.7.5"
|
||||||
@@ -71,7 +72,7 @@ defmt = { version = "0.3", optional = true }
|
|||||||
defmt-rtt = "0.4.2"
|
defmt-rtt = "0.4.2"
|
||||||
|
|
||||||
embedded-sdmmc = { version = "0.9", default-features = false }
|
embedded-sdmmc = { version = "0.9", default-features = false }
|
||||||
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "2a484aaab5f6b9824cc813fe4ae087250c9e39c1" } # async branch
|
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "1d15123929fa7ef73d5d6aead7faf1bba50ce915" } # async branch
|
||||||
embedded-graphics = { version = "0.8.1" }
|
embedded-graphics = { version = "0.8.1" }
|
||||||
embedded-text = "0.7.2"
|
embedded-text = "0.7.2"
|
||||||
embedded-layout = "0.4.2"
|
embedded-layout = "0.4.2"
|
||||||
|
|||||||
@@ -37,12 +37,8 @@ pub extern "Rust" fn sleep(ticks: u64) {
|
|||||||
// TODO: maybe return result
|
// TODO: maybe return result
|
||||||
pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
|
pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
|
||||||
loop {
|
loop {
|
||||||
let fb = FRAMEBUFFER.get().try_lock();
|
unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() }
|
||||||
if let Ok(mut fb) = fb {
|
return;
|
||||||
fb.draw_iter(pixels.iter().copied()).unwrap();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sleep(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
|
use crate::framebuffer::AtomicFrameBuffer;
|
||||||
use embassy_rp::{
|
use embassy_rp::{
|
||||||
Peri,
|
Peri,
|
||||||
gpio::{Level, Output},
|
gpio::{Level, Output},
|
||||||
peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
|
peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
|
||||||
spi::{Async, Spi},
|
spi::{Async, Spi},
|
||||||
};
|
};
|
||||||
use embassy_sync::{
|
|
||||||
blocking_mutex::raw::CriticalSectionRawMutex, lazy_lock::LazyLock, mutex::Mutex,
|
|
||||||
};
|
|
||||||
use embassy_time::{Delay, Timer};
|
use embassy_time::{Delay, Timer};
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
draw_target::DrawTarget,
|
draw_target::DrawTarget,
|
||||||
pixelcolor::{Rgb565, RgbColor},
|
pixelcolor::{Rgb565, RgbColor},
|
||||||
};
|
};
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
use st7365p_lcd::{FrameBuffer, ST7365P};
|
use st7365p_lcd::ST7365P;
|
||||||
|
|
||||||
type DISPLAY = ST7365P<
|
type DISPLAY = ST7365P<
|
||||||
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
|
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
|
||||||
@@ -25,9 +23,7 @@ type DISPLAY = ST7365P<
|
|||||||
pub const SCREEN_WIDTH: usize = 320;
|
pub const SCREEN_WIDTH: usize = 320;
|
||||||
pub const SCREEN_HEIGHT: usize = 320;
|
pub const SCREEN_HEIGHT: usize = 320;
|
||||||
|
|
||||||
type FB = FrameBuffer<SCREEN_WIDTH, SCREEN_HEIGHT, { SCREEN_WIDTH * SCREEN_HEIGHT }>;
|
pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new();
|
||||||
pub static FRAMEBUFFER: LazyLock<Mutex<CriticalSectionRawMutex, FB>> =
|
|
||||||
LazyLock::new(|| Mutex::new(FrameBuffer::new()));
|
|
||||||
|
|
||||||
pub async fn init_display(
|
pub async fn init_display(
|
||||||
spi: Spi<'static, SPI1, Async>,
|
spi: Spi<'static, SPI1, Async>,
|
||||||
@@ -44,26 +40,25 @@ pub async fn init_display(
|
|||||||
true,
|
true,
|
||||||
Delay,
|
Delay,
|
||||||
);
|
);
|
||||||
let mut fb = FRAMEBUFFER.get().lock().await;
|
|
||||||
display.init().await.unwrap();
|
display.init().await.unwrap();
|
||||||
display.set_custom_orientation(0x40).await.unwrap();
|
display.set_custom_orientation(0x40).await.unwrap();
|
||||||
display.draw(&mut fb).await.unwrap();
|
unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() }
|
||||||
display.set_on().await.unwrap();
|
display.set_on().await.unwrap();
|
||||||
|
|
||||||
display
|
display
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_fb() {
|
pub async fn clear_fb() {
|
||||||
let mut fb = FRAMEBUFFER.get().lock().await;
|
unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() }
|
||||||
let fb = &mut *fb;
|
|
||||||
fb.clear(Rgb565::BLACK).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn display_handler(mut display: DISPLAY) {
|
pub async fn display_handler(mut display: DISPLAY) {
|
||||||
loop {
|
loop {
|
||||||
{
|
unsafe {
|
||||||
let mut fb = FRAMEBUFFER.get().lock().await;
|
FRAMEBUFFER
|
||||||
display.partial_draw_batched(&mut fb).await.unwrap();
|
.partial_draw_batched(&mut display)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer::after_millis(32).await; // 30 fps
|
Timer::after_millis(32).await; // 30 fps
|
||||||
|
|||||||
432
kernel/src/framebuffer.rs
Normal file
432
kernel/src/framebuffer.rs
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
use crate::display::{SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use embassy_sync::lazy_lock::LazyLock;
|
||||||
|
use embedded_graphics::{
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
pixelcolor::{
|
||||||
|
Rgb565,
|
||||||
|
raw::{RawData, RawU16},
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
primitives::Rectangle,
|
||||||
|
};
|
||||||
|
use embedded_hal_2::digital::OutputPin;
|
||||||
|
use embedded_hal_async::{delay::DelayNs, spi::SpiDevice};
|
||||||
|
use heapless::Vec;
|
||||||
|
use st7365p_lcd::{FrameBuffer, ST7365P};
|
||||||
|
|
||||||
|
pub const TILE_SIZE: usize = 16; // 16x16 tile
|
||||||
|
pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles
|
||||||
|
|
||||||
|
// Group of tiles for batching
|
||||||
|
pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer
|
||||||
|
type MetaTileVec = heapless::Vec<Rectangle, { TILE_COUNT / MAX_META_TILES }>;
|
||||||
|
|
||||||
|
const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH;
|
||||||
|
|
||||||
|
static mut BUFFER: [u16; SIZE] = [0; SIZE];
|
||||||
|
|
||||||
|
static mut DIRTY_TILES: LazyLock<Vec<AtomicBool, TILE_COUNT>> = LazyLock::new(|| {
|
||||||
|
let mut tiles = Vec::new();
|
||||||
|
for _ in 0..TILE_COUNT {
|
||||||
|
tiles.push(AtomicBool::new(true));
|
||||||
|
}
|
||||||
|
tiles
|
||||||
|
});
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct AtomicFrameBuffer;
|
||||||
|
|
||||||
|
impl AtomicFrameBuffer {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_tiles_dirty(&mut self, rect: Rectangle) {
|
||||||
|
let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
|
||||||
|
let start_tx = (rect.top_left.x as usize) / TILE_SIZE;
|
||||||
|
let end_tx = ((rect.top_left.x + rect.size.width as i32 - 1) as usize) / TILE_SIZE;
|
||||||
|
let start_ty = (rect.top_left.y as usize) / TILE_SIZE;
|
||||||
|
let end_ty = ((rect.top_left.y + rect.size.height as i32 - 1) as usize) / TILE_SIZE;
|
||||||
|
|
||||||
|
for ty in start_ty..=end_ty {
|
||||||
|
for tx in start_tx..=end_tx {
|
||||||
|
let tile_idx = ty * tiles_x + tx;
|
||||||
|
unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Relaxed) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> {
|
||||||
|
unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color };
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pixels_buffered<P: IntoIterator<Item = u16>>(
|
||||||
|
&mut self,
|
||||||
|
sx: u16,
|
||||||
|
sy: u16,
|
||||||
|
ex: u16,
|
||||||
|
ey: u16,
|
||||||
|
colors: P,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
if sx >= self.size().width as u16 - 1
|
||||||
|
|| ex >= self.size().width as u16 - 1
|
||||||
|
|| sy >= self.size().height as u16 - 1
|
||||||
|
|| ey >= self.size().height as u16 - 1
|
||||||
|
{
|
||||||
|
return Err(()); // Bounds check
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut color_iter = colors.into_iter();
|
||||||
|
|
||||||
|
for y in sy..=ey {
|
||||||
|
for x in sx..=ex {
|
||||||
|
if let Some(color) = color_iter.next() {
|
||||||
|
unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color };
|
||||||
|
} else {
|
||||||
|
return Err(()); // Not enough data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: check that we consumed *exactly* the right amount
|
||||||
|
if color_iter.next().is_some() {
|
||||||
|
return Err(()); // Too much data
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates
|
||||||
|
fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec {
|
||||||
|
let mut meta_tiles: MetaTileVec = Vec::new();
|
||||||
|
|
||||||
|
for ty in 0..tiles_y {
|
||||||
|
let mut tx = 0;
|
||||||
|
while tx < tiles_x {
|
||||||
|
let idx = ty * tiles_x + tx;
|
||||||
|
if !unsafe { DIRTY_TILES.get()[idx].load(Ordering::Acquire) } {
|
||||||
|
tx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start meta-tile at this tile
|
||||||
|
let mut width_tiles = 1;
|
||||||
|
let height_tiles = 1;
|
||||||
|
|
||||||
|
// Grow horizontally, but keep under MAX_TILES_PER_METATILE
|
||||||
|
while tx + width_tiles < tiles_x
|
||||||
|
&& unsafe {
|
||||||
|
DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
&& (width_tiles + height_tiles) <= MAX_META_TILES
|
||||||
|
{
|
||||||
|
width_tiles += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: for simplicity, skipped vertical growth
|
||||||
|
|
||||||
|
for x_off in 0..width_tiles {
|
||||||
|
unsafe {
|
||||||
|
DIRTY_TILES.get()[ty * tiles_x + tx + x_off]
|
||||||
|
.store(false, Ordering::Release);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// new meta-tile pos
|
||||||
|
let rect = Rectangle::new(
|
||||||
|
Point::new((tx * TILE_SIZE) as i32, (ty * TILE_SIZE) as i32),
|
||||||
|
Size::new(
|
||||||
|
(width_tiles * TILE_SIZE) as u32,
|
||||||
|
(height_tiles * TILE_SIZE) as u32,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if meta_tiles.push(rect).is_err() {
|
||||||
|
return meta_tiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
tx += width_tiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta_tiles
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the entire framebuffer to the display
|
||||||
|
pub async fn draw<SPI, DC, RST, DELAY: DelayNs>(
|
||||||
|
&mut self,
|
||||||
|
display: &mut ST7365P<SPI, DC, RST, DELAY>,
|
||||||
|
) -> Result<(), ()>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
{
|
||||||
|
display
|
||||||
|
.set_pixels_buffered(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.size().width as u16 - 1,
|
||||||
|
self.size().height as u16 - 1,
|
||||||
|
unsafe { &BUFFER },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
for tile in DIRTY_TILES.get_mut().iter() {
|
||||||
|
tile.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends only dirty tiles (16x16px) individually to the display without batching
|
||||||
|
pub async fn partial_draw<SPI, DC, RST, DELAY: DelayNs>(
|
||||||
|
&mut self,
|
||||||
|
display: &mut ST7365P<SPI, DC, RST, DELAY>,
|
||||||
|
) -> Result<(), ()>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
{
|
||||||
|
if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } {
|
||||||
|
let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
|
||||||
|
let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE;
|
||||||
|
|
||||||
|
let mut tile_buffer = [0u16; TILE_SIZE * TILE_SIZE];
|
||||||
|
|
||||||
|
for ty in 0..tiles_y {
|
||||||
|
for tx in 0..tiles_x {
|
||||||
|
if unsafe { !DIRTY_TILES.get()[ty * tiles_x + tx].load(Ordering::Acquire) } {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = tx * TILE_SIZE;
|
||||||
|
let y = ty * TILE_SIZE;
|
||||||
|
|
||||||
|
// Copy pixels for the tile into tile_buffer
|
||||||
|
for row in 0..TILE_SIZE {
|
||||||
|
for col in 0..TILE_SIZE {
|
||||||
|
let actual_x = x + col;
|
||||||
|
let actual_y = y + row;
|
||||||
|
|
||||||
|
if actual_x < SCREEN_WIDTH && actual_y < SCREEN_HEIGHT {
|
||||||
|
let idx = actual_y * SCREEN_WIDTH + actual_x;
|
||||||
|
tile_buffer[row * TILE_SIZE + col] = unsafe { BUFFER[idx] };
|
||||||
|
} else {
|
||||||
|
// Out of bounds, fill with zero (or background)
|
||||||
|
tile_buffer[row * TILE_SIZE + col] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the tile's pixel data to the display
|
||||||
|
display
|
||||||
|
.set_pixels_buffered(
|
||||||
|
x as u16,
|
||||||
|
y as u16,
|
||||||
|
(x + TILE_SIZE - 1).min(SCREEN_WIDTH - 1) as u16,
|
||||||
|
(y + TILE_SIZE - 1).min(SCREEN_HEIGHT - 1) as u16,
|
||||||
|
&tile_buffer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Mark tile as clean
|
||||||
|
unsafe {
|
||||||
|
DIRTY_TILES.get_mut()[ty * tiles_x + tx].store(false, Ordering::Release)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends only dirty tiles (16x16px) in batches to the display
|
||||||
|
pub async fn partial_draw_batched<SPI, DC, RST, DELAY>(
|
||||||
|
&mut self,
|
||||||
|
display: &mut ST7365P<SPI, DC, RST, DELAY>,
|
||||||
|
) -> Result<(), ()>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } {
|
||||||
|
let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
|
||||||
|
let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE;
|
||||||
|
|
||||||
|
let meta_tiles = self.find_meta_tiles(tiles_x, tiles_y);
|
||||||
|
|
||||||
|
// buffer for copying meta tiles before sending to display
|
||||||
|
let mut pixel_buffer: heapless::Vec<u16, { MAX_META_TILES * TILE_SIZE * TILE_SIZE }> =
|
||||||
|
Vec::new();
|
||||||
|
|
||||||
|
for rect in meta_tiles {
|
||||||
|
let rect_width = rect.size.width as usize;
|
||||||
|
let rect_height = rect.size.height as usize;
|
||||||
|
let rect_x = rect.top_left.x as usize;
|
||||||
|
let rect_y = rect.top_left.y as usize;
|
||||||
|
|
||||||
|
pixel_buffer.clear();
|
||||||
|
|
||||||
|
for row in 0..rect_height {
|
||||||
|
let y = rect_y + row;
|
||||||
|
let start = y * SCREEN_WIDTH + rect_x;
|
||||||
|
let end = start + rect_width;
|
||||||
|
|
||||||
|
// Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS
|
||||||
|
pixel_buffer
|
||||||
|
.extend_from_slice(unsafe { &BUFFER[start..end] })
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
display
|
||||||
|
.set_pixels_buffered(
|
||||||
|
rect_x as u16,
|
||||||
|
rect_y as u16,
|
||||||
|
(rect_x + rect_width - 1) as u16,
|
||||||
|
(rect_y + rect_height - 1) as u16,
|
||||||
|
&pixel_buffer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// walk the meta-tile and set as clean
|
||||||
|
let start_tx = rect_x / TILE_SIZE;
|
||||||
|
let start_ty = rect_y / TILE_SIZE;
|
||||||
|
let end_tx = (rect_x + rect_width - 1) / TILE_SIZE;
|
||||||
|
let end_ty = (rect_y + rect_height - 1) / TILE_SIZE;
|
||||||
|
|
||||||
|
for ty in start_ty..=end_ty {
|
||||||
|
for tx in start_tx..=end_tx {
|
||||||
|
let tile_idx = ty * tiles_x + tx;
|
||||||
|
unsafe { DIRTY_TILES.get_mut()[tile_idx].store(false, Ordering::Release) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawTarget for AtomicFrameBuffer {
|
||||||
|
type Error = ();
|
||||||
|
type Color = Rgb565;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
let mut dirty_rect: Option<Rectangle> = None;
|
||||||
|
|
||||||
|
for Pixel(coord, color) in pixels {
|
||||||
|
if coord.x >= 0 && coord.y >= 0 {
|
||||||
|
let x = coord.x as i32;
|
||||||
|
let y = coord.y as i32;
|
||||||
|
|
||||||
|
if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT {
|
||||||
|
unsafe {
|
||||||
|
BUFFER[(y as usize) * SCREEN_WIDTH + (x as usize)] =
|
||||||
|
RawU16::from(color).into_inner()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref mut rect) = dirty_rect {
|
||||||
|
rect.top_left.x = rect.top_left.x.min(x);
|
||||||
|
rect.top_left.y = rect.top_left.y.min(y);
|
||||||
|
let max_x = (rect.top_left.x + rect.size.width as i32 - 1).max(x);
|
||||||
|
let max_y = (rect.top_left.y + rect.size.height as i32 - 1).max(y);
|
||||||
|
rect.size.width = (max_x - rect.top_left.x + 1) as u32;
|
||||||
|
rect.size.height = (max_y - rect.top_left.y + 1) as u32;
|
||||||
|
} else {
|
||||||
|
dirty_rect = Some(Rectangle::new(Point::new(x, y), Size::new(1, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rect) = dirty_rect {
|
||||||
|
self.mark_tiles_dirty(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Self::Color>,
|
||||||
|
{
|
||||||
|
let drawable_area = area.intersection(&Rectangle::new(Point::zero(), self.size()));
|
||||||
|
|
||||||
|
if drawable_area.size != Size::zero() {
|
||||||
|
// We assume that `colors` iterator is in row-major order for the original `area`
|
||||||
|
// So we must skip rows/pixels that are clipped
|
||||||
|
let area_width = area.size.width;
|
||||||
|
let area_height = area.size.height;
|
||||||
|
let mut colors = colors.into_iter();
|
||||||
|
|
||||||
|
for y in 0..area_height {
|
||||||
|
for x in 0..area_width {
|
||||||
|
let p = area.top_left + Point::new(x as i32, y as i32);
|
||||||
|
|
||||||
|
if drawable_area.contains(p) {
|
||||||
|
if let Some(color) = colors.next() {
|
||||||
|
self.set_pixel(
|
||||||
|
p.x as u16,
|
||||||
|
p.y as u16,
|
||||||
|
RawU16::from(color).into_inner(),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Still need to consume the color even if not used!
|
||||||
|
let _ = colors.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mark_tiles_dirty(*area);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
|
||||||
|
self.fill_contiguous(
|
||||||
|
area,
|
||||||
|
core::iter::repeat(color).take((self.size().width * self.size().height) as usize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
|
||||||
|
self.set_pixels_buffered(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.size().width as u16 - 1,
|
||||||
|
self.size().height as u16 - 1,
|
||||||
|
core::iter::repeat(RawU16::from(color).into_inner())
|
||||||
|
.take((self.size().width * self.size().height) as usize),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
for tile in DIRTY_TILES.get_mut().iter() {
|
||||||
|
tile.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginDimensions for AtomicFrameBuffer {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ extern crate alloc;
|
|||||||
mod abi;
|
mod abi;
|
||||||
mod display;
|
mod display;
|
||||||
mod elf;
|
mod elf;
|
||||||
|
mod framebuffer;
|
||||||
mod peripherals;
|
mod peripherals;
|
||||||
mod scsi;
|
mod scsi;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|||||||
@@ -74,9 +74,8 @@ async fn draw_selection() {
|
|||||||
guard.selections.clone()
|
guard.selections.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut fb = FRAMEBUFFER.get().lock().await;
|
|
||||||
let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE);
|
let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE);
|
||||||
let display_area = fb.bounding_box();
|
let display_area = unsafe { FRAMEBUFFER.bounding_box() };
|
||||||
|
|
||||||
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
|
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
|
||||||
let no_bins = String::from_str(NO_BINS).unwrap();
|
let no_bins = String::from_str(NO_BINS).unwrap();
|
||||||
@@ -90,13 +89,13 @@ async fn draw_selection() {
|
|||||||
),
|
),
|
||||||
text_style,
|
text_style,
|
||||||
)
|
)
|
||||||
.draw(&mut *fb)
|
.draw(unsafe { &mut FRAMEBUFFER })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
let mut file_names = file_names.iter();
|
let mut file_names = file_names.iter();
|
||||||
let Some(first) = file_names.next() else {
|
let Some(first) = file_names.next() else {
|
||||||
Text::new(NO_BINS, Point::zero(), text_style)
|
Text::new(NO_BINS, Point::zero(), text_style)
|
||||||
.draw(&mut *fb)
|
.draw(unsafe { &mut FRAMEBUFFER })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -116,7 +115,7 @@ async fn draw_selection() {
|
|||||||
.with_alignment(horizontal::Center)
|
.with_alignment(horizontal::Center)
|
||||||
.arrange()
|
.arrange()
|
||||||
.align_to(&display_area, horizontal::Center, vertical::Center)
|
.align_to(&display_area, horizontal::Center, vertical::Center)
|
||||||
.draw(&mut *fb)
|
.draw(unsafe { &mut FRAMEBUFFER })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user