From e475d66842a1523705cf9ea537f2bb456cf9c9c7 Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Mon, 28 Oct 2024 22:22:03 +0100 Subject: [PATCH] src: rewrite display to have generic surface implementations --- src/embedded_graphics_lib.rs | 70 +++++++++++++++------- src/main.rs | 26 ++++---- src/power.rs | 15 +---- src/render.rs | 113 +++++++++++++++++++++++++---------- 4 files changed, 146 insertions(+), 78 deletions(-) diff --git a/src/embedded_graphics_lib.rs b/src/embedded_graphics_lib.rs index 25bebef..096a15f 100644 --- a/src/embedded_graphics_lib.rs +++ b/src/embedded_graphics_lib.rs @@ -3,23 +3,40 @@ use embedded_graphics::{ pixelcolor::Rgb888, primitives::Rectangle }; +use embedded_graphics::pixelcolor::RgbColor; use ws2812_esp32_rmt_driver::lib_embedded_graphics::{Ws2812DrawTarget, LedPixelShape}; -use std::rc::Rc; -use std::cell::RefCell; -use std::sync::{Arc, Mutex}; use running_average::RealTimeRunningAverage; +use std::io; use crate::power; +use crate::power::AsMilliwatts; use crate::lib8::*; use crate::render::*; use crate::time::Periodically; +use crate::task::Task; use crate::geometry::*; -pub struct EmbeddedDisplay +impl AsMilliwatts for T { + fn as_milliwatts(&self) -> u32 { + const RED_MW : u32 = 16 * 5; //< 16mA @ 5v = 80mW + const GREEN_MW : u32 = 11 * 5; //< 11mA @ 5v = 55mW + const BLUE_MW : u32 = 15 * 5; //< 15mA @ 5v = 75mW + const DARK_MW : u32 = 1 * 5; //< 1mA @ 5v = 5mW + + let red = (self.r() as u32 * RED_MW).wrapping_shr(8); + let green = (self.g() as u32 * GREEN_MW).wrapping_shr(8); + let blue = (self.b() as u32 * BLUE_MW).wrapping_shr(8); + + return red + green + blue + DARK_MW; + } +} + +pub struct EmbeddedDisplay where -T: DrawTarget { - surfaces : RefCell>, +T: DrawTarget, +S: Surface + Default + Clone { + surfaces : SurfacePool, target: T, total_mw: u32, max_mw: u32, @@ -28,12 +45,27 @@ T: DrawTarget { fps_display: Periodically } -impl EmbeddedDisplay +impl Task for EmbeddedDisplay, S> { + fn start(&mut self) { + self.target.set_brightness(0); + } + + fn name(&self) -> &'static str { "Renderer" } + + fn tick(&mut self) { + self.start_frame(); + self.render_frame(); + self.end_frame(); + } +} + +impl EmbeddedDisplay where -T: DrawTarget { +T: DrawTarget, +S: Surface + Default + Clone { pub fn new(target: T, max_mw: u32) -> Self { EmbeddedDisplay { - surfaces: RefCell::new(Vec::new()), + surfaces: SurfacePool::new(), target: target, max_mw: max_mw, total_mw: 0, @@ -44,17 +76,17 @@ T: DrawTarget { } } -impl Surfaces for EmbeddedDisplay +impl Surfaces for EmbeddedDisplay where -T: DrawTarget { - fn new_surface(&mut self) -> Surface { - let surface = Surface::new(); - self.surfaces.borrow_mut().push(surface.clone()); - return surface; +T: DrawTarget, +S: Surface + Default + Clone { + fn new_surface(&mut self) -> Result { + self.surfaces.new_surface() } } -impl Display for EmbeddedDisplay> { +impl Display for EmbeddedDisplay, S> { + fn start_frame(&mut self) { self.total_mw = 0; self.frame = self.frame.wrapping_add(1); @@ -76,19 +108,17 @@ impl Display for EmbeddedDisplay> { let yStride: u8 = 255 / (size.height as u8); let area = Rectangle::new(Point::new(0, 0), size); - let surfaces = self.surfaces.borrow(); - self.target.draw_iter( area.points() .map(|pos| { let virtCoords = VirtualCoordinates::new(pos.x as u8 * xStride, pos.y as u8 * yStride); let mut pixel = RGB8::new(0, 0, 0); - for surface in surfaces.iter() { + for surface in self.surfaces.iter() { surface.with_shader(|shader| { pixel = shader.draw(virtCoords.clone()); }) } - self.total_mw += power::color_to_mw(&pixel); + self.total_mw += pixel.as_milliwatts(); return Pixel(pos, Rgb888::new(pixel.red, pixel.green, pixel.blue)); }) ).unwrap(); diff --git a/src/main.rs b/src/main.rs index 31011b2..656567b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ #![feature(trait_upcasting)] #![allow(arithmetic_overflow)] use esp_idf_svc::hal::prelude::Peripherals; -use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelStrip, LedPixelShape, LedPixelMatrix, Ws2812DrawTarget}; +use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelShape, Ws2812DrawTarget}; use embedded_graphics::{ prelude::*, }; use palette::Hsv; use palette::convert::IntoColorUnclamped; -use std::thread; mod power; mod lib8; @@ -20,10 +19,11 @@ mod embedded_graphics_lib; use crate::time::Periodically; use crate::geometry::{Coordinates, VirtualCoordinates}; use crate::embedded_graphics_lib::EmbeddedDisplay; +use crate::render::{Surfaces, Surface, SimpleSurface}; -struct IdleTask { +struct IdleTask { frame: u8, - surface: render::Surface, + surface: T, updater: Periodically } @@ -37,17 +37,17 @@ impl render::Shader for IdleShader { } } -impl IdleTask { - fn new(render: &mut dyn render::Display) -> Self { +impl IdleTask { + fn new(surface: T) -> Self { IdleTask { frame: 0, - surface: render.new_surface(), + surface: surface, updater: Periodically::new_every_n_ms(16) } } } -impl task::Task for IdleTask { +impl task::Task for IdleTask { fn name(&self) -> &'static str { "Idle" } fn tick(&mut self) { @@ -82,6 +82,8 @@ impl LedPixelShape for PonderjarMatrix { } } +type PonderjarTarget<'a> = Ws2812DrawTarget<'a, PonderjarMatrix>; + fn main() { // It is necessary to call this function once. Otherwise some patches to the runtime // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 @@ -96,19 +98,17 @@ fn main() { let led_pin = peripherals.pins.gpio14; let channel = peripherals.rmt.channel0; - const NUM_PIXELS : usize = 255; const POWER_VOLTS : u32 = 5; const POWER_MA : u32 = 500; const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA; log::info!("Setting up display"); - let mut target = Ws2812DrawTarget::::new(channel, led_pin).unwrap(); - target.set_brightness(0); - let mut display = EmbeddedDisplay::new(target, MAX_POWER_MW); + let target = PonderjarTarget::new(channel, led_pin).unwrap(); + let mut display = EmbeddedDisplay::::new(target, MAX_POWER_MW); log::info!("Creating runner"); let mut runner = task::Scheduler::new(vec![ - Box::new(IdleTask::new(&mut display)), + Box::new(IdleTask::new(display.new_surface().unwrap())), Box::new(display), ]); diff --git a/src/power.rs b/src/power.rs index e4ba026..b40cb6a 100644 --- a/src/power.rs +++ b/src/power.rs @@ -1,16 +1,5 @@ -use embedded_graphics::pixelcolor::RgbColor; - -pub fn color_to_mw(color : &T) -> u32 { - const RED_MW : u32 = 16 * 5; //< 16mA @ 5v = 80mW - const GREEN_MW : u32 = 11 * 5; //< 11mA @ 5v = 55mW - const BLUE_MW : u32 = 15 * 5; //< 15mA @ 5v = 75mW - const DARK_MW : u32 = 1 * 5; //< 1mA @ 5v = 5mW - - let red = (color.r() as u32 * RED_MW).wrapping_shr(8); - let green = (color.g() as u32 * GREEN_MW).wrapping_shr(8); - let blue = (color.b() as u32 * BLUE_MW).wrapping_shr(8); - - return red + green + blue + DARK_MW; +pub trait AsMilliwatts { + fn as_milliwatts(&self) -> u32; } pub fn brightness_for_mw(total_mw : u32, target : u8, max_power: u32) -> u8 { diff --git a/src/render.rs b/src/render.rs index bd877c2..84201e4 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,55 +1,47 @@ use std::rc::Rc; use std::cell::RefCell; use std::sync::{Arc, Mutex}; -use palette::blend::{BlendWith, Equations, Parameter, PreAlpha}; +use std::io; -use crate::task; use crate::lib8::RGB8; -use crate::power; -use crate::time::Periodically; use crate::geometry::*; -use std::time::Instant; - pub trait Shader: Send { fn draw(&self, surface_coords: VirtualCoordinates) -> RGB8; } -pub trait Surfaces { - fn new_surface(&mut self) -> Surface; +pub trait Surface { + fn with_shader(&self, f: F); + fn set_shader(&mut self, shader: Box); + fn clear_shader(&mut self); + fn set_opacity(&mut self, opacity: u8); } -pub trait Display: Surfaces { +pub trait Surfaces { + fn new_surface(&mut self) -> Result; +} + +pub trait Display: Surfaces { fn start_frame(&mut self) {} fn end_frame(&mut self) {} - - fn render_frame(&mut self) {} + fn render_frame(&mut self); } -impl task::Task for T -where -T: Display { - fn name(&self) -> &'static str { "Renderer" } - - fn tick(&mut self) { - self.start_frame(); - self.render_frame(); - self.end_frame(); - } -} - -struct ShaderBinding { +pub struct ShaderBinding { shader: Option>, opacity: u8, } #[derive(Clone)] -pub struct Surface { - pub binding: Arc> +pub struct BoundSurface { + pub binding: T } -impl Surface { - pub fn new() -> Self { +pub type SharedSurface = BoundSurface>>; +pub type SimpleSurface = BoundSurface>>; + +impl Default for BoundSurface>> { + fn default() -> Self { Self { binding: Arc::new(Mutex::new(ShaderBinding { shader: None, @@ -57,22 +49,79 @@ impl Surface { })), } } +} - pub fn with_shader(&self, f: F) { +impl Default for BoundSurface>>{ + fn default() -> Self { + Self { + binding: Rc::new(RefCell::new(ShaderBinding { + shader: None, + opacity: 255, + })), + } + } +} + +impl Surface for BoundSurface>> { + fn with_shader(&self, mut f: F) { + if let Some(ref shader) = self.binding.borrow().shader { + f(shader.as_ref()); + } + } + + fn set_shader(&mut self, shader: Box) { + self.binding.borrow_mut().shader = Some(shader); + } + + fn clear_shader(&mut self) { + self.binding.borrow_mut().shader = None; + } + + fn set_opacity(&mut self, opacity: u8) { + self.binding.borrow_mut().opacity = opacity; + } +} + +impl Surface for BoundSurface>> { + fn with_shader(&self, mut f: F) { if let Some(ref shader) = self.binding.lock().unwrap().shader { f(shader.as_ref()); } } - pub fn set_shader(&mut self, shader: Box) { + fn set_shader(&mut self, shader: Box) { self.binding.lock().unwrap().shader = Some(shader); } - pub fn clear_shader(&mut self) { + fn clear_shader(&mut self) { self.binding.lock().unwrap().shader = None; } - pub fn set_opacity(&mut self, opacity: u8) { + fn set_opacity(&mut self, opacity: u8) { self.binding.lock().unwrap().opacity = opacity; } } + +pub struct SurfacePool { + surfaces: Vec +} + +impl SurfacePool { + pub const fn new() -> Self { + Self { + surfaces: Vec::new() + } + } + + pub fn iter(&self) -> std::slice::Iter { + self.surfaces.iter() + } +} + +impl Surfaces for SurfacePool { + fn new_surface(&mut self) -> Result { + let surface = S::default(); + self.surfaces.push(surface.clone()); + return Ok(surface); + } +}