From 3f20c07369f1434f9442f5a857b704566ec5442f Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Sun, 27 Oct 2024 15:14:28 +0100 Subject: [PATCH] renderbug: first implementation of virtual coordinate based rendering --- src/embedded_graphics_lib.rs | 96 ++++++++++++++++++++++++++++ src/geometry.rs | 53 ++++++++++++++++ src/lib8.rs | 3 +- src/main.rs | 41 ++++++++++-- src/render.rs | 118 +++++++++-------------------------- src/task.rs | 7 ++- 6 files changed, 221 insertions(+), 97 deletions(-) create mode 100644 src/embedded_graphics_lib.rs create mode 100644 src/geometry.rs diff --git a/src/embedded_graphics_lib.rs b/src/embedded_graphics_lib.rs new file mode 100644 index 0000000..25bebef --- /dev/null +++ b/src/embedded_graphics_lib.rs @@ -0,0 +1,96 @@ +use embedded_graphics::{ + prelude::*, + pixelcolor::Rgb888, + primitives::Rectangle +}; +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 crate::power; +use crate::lib8::*; +use crate::render::*; +use crate::time::Periodically; +use crate::geometry::*; + +pub struct EmbeddedDisplay +where +T: DrawTarget { + surfaces : RefCell>, + target: T, + total_mw: u32, + max_mw: u32, + fps: RealTimeRunningAverage, + frame: u32, + fps_display: Periodically +} + +impl EmbeddedDisplay +where +T: DrawTarget { + pub fn new(target: T, max_mw: u32) -> Self { + EmbeddedDisplay { + surfaces: RefCell::new(Vec::new()), + target: target, + max_mw: max_mw, + total_mw: 0, + fps: RealTimeRunningAverage::default(), + frame: 0, + fps_display: Periodically::new_every_n_seconds(5) + } + } +} + +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; + } +} + +impl Display for EmbeddedDisplay> { + fn start_frame(&mut self) { + self.total_mw = 0; + self.frame = self.frame.wrapping_add(1); + } + + fn end_frame(&mut self) { + let brightness = power::brightness_for_mw(self.total_mw, 255, self.max_mw); + self.target.set_brightness(brightness); + self.target.flush().unwrap(); + self.fps.insert(1); + self.fps_display.run(|| { + log::info!("FPS: {} frame={} brightness={} mw={}", self.fps.measurement(), self.frame, brightness, self.total_mw); + }); + } + + fn render_frame(&mut self) { + let size = T::size(); + let xStride: u8 = 255 / (size.width as u8); + 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() { + surface.with_shader(|shader| { + pixel = shader.draw(virtCoords.clone()); + }) + } + self.total_mw += power::color_to_mw(&pixel); + return Pixel(pos, Rgb888::new(pixel.red, pixel.green, pixel.blue)); + }) + ).unwrap(); + } +} diff --git a/src/geometry.rs b/src/geometry.rs new file mode 100644 index 0000000..061b7f5 --- /dev/null +++ b/src/geometry.rs @@ -0,0 +1,53 @@ +use std::marker::PhantomData; + +pub trait CoordinateSpace {} + +pub trait Coordinates { + fn x(&self) -> T; + fn y(&self) -> T; + fn new(x: T, y: T) -> Self; + + const MAX: T; + const MIN: T; +} + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct Virtual {} +impl CoordinateSpace for Virtual {} + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct Physical {} +impl CoordinateSpace for Physical {} + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct Coord8 { + x: u8, + y: u8, + space: PhantomData +} + +pub type VirtualCoordinates = Coord8; +pub type PhysicalCoordinates = Coord8; + +impl Coordinates for Coord8 +where +S: CoordinateSpace { + fn new(x: u8, y: u8) -> Self { + Self { + x: x, + y: y, + space: PhantomData + } + } + + fn x(&self) -> u8 { + self.x + } + + fn y(&self) -> u8 { + self.y + } + + const MAX: u8 = 255; + const MIN: u8 = 255; +} diff --git a/src/lib8.rs b/src/lib8.rs index d27367d..6d8e3f2 100644 --- a/src/lib8.rs +++ b/src/lib8.rs @@ -1,4 +1,5 @@ use palette::convert::FromColorUnclamped; +use palette::blend::{PreAlpha, Premultiply}; use palette::encoding::srgb::Srgb; use palette::Hsv; use embedded_graphics::pixelcolor::RgbColor; @@ -13,7 +14,7 @@ pub struct RGB8 { } impl RGB8 { - const fn new(red : u8, green : u8, blue : u8) -> Self { + pub const fn new(red : u8, green : u8, blue : u8) -> Self { Self { red: red, green: green, diff --git a/src/main.rs b/src/main.rs index c710420..31011b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![feature(trait_upcasting)] #![allow(arithmetic_overflow)] use esp_idf_svc::hal::prelude::Peripherals; -use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelStrip, Ws2812DrawTarget}; +use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelStrip, LedPixelShape, LedPixelMatrix, Ws2812DrawTarget}; use embedded_graphics::{ prelude::*, }; @@ -14,8 +14,12 @@ mod lib8; mod render; mod task; mod time; +mod geometry; +mod embedded_graphics_lib; use crate::time::Periodically; +use crate::geometry::{Coordinates, VirtualCoordinates}; +use crate::embedded_graphics_lib::EmbeddedDisplay; struct IdleTask { frame: u8, @@ -28,8 +32,8 @@ struct IdleShader { } impl render::Shader for IdleShader { - fn draw(&self, coords: Point) -> lib8::RGB8 { - Hsv::new_srgb(self.frame.wrapping_add(coords.x as u8), 255, 255).into_color_unclamped() + fn draw(&self, coords: VirtualCoordinates) -> lib8::RGB8 { + Hsv::new_srgb(self.frame.wrapping_add(coords.x()).wrapping_add(coords.y()), 255, 255).into_color_unclamped() } } @@ -52,6 +56,30 @@ impl task::Task for IdleTask { self.surface.set_shader(Box::new(IdleShader { frame: self.frame })); }) } + + fn stop(&mut self) { + self.surface.clear_shader(); + } +} + +struct PonderjarMatrix {} + +impl LedPixelShape for PonderjarMatrix { + fn size() -> Size { + Size::new(17, 17) + } + + fn pixel_index(point: Point) -> Option { + if (0..Self::size().width as i32).contains(&point.x) && (0..Self::size().height as i32).contains(&point.y) { + if point.y % 2 == 0 { + Some((point.y as u32 * Self::size().width as u32 + point.x as u32).try_into().unwrap()) + } else { + Some((point.y as u32 * Self::size().width as u32 - point.x as u32).try_into().unwrap()) + } + } else { + None + } + } } fn main() { @@ -68,14 +96,15 @@ fn main() { let led_pin = peripherals.pins.gpio14; let channel = peripherals.rmt.channel0; - const NUM_PIXELS : usize = 300; + 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 target = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); - let mut display = render::EmbeddedDisplay::new(target, MAX_POWER_MW); + let mut target = Ws2812DrawTarget::::new(channel, led_pin).unwrap(); + target.set_brightness(0); + let mut display = EmbeddedDisplay::new(target, MAX_POWER_MW); log::info!("Creating runner"); let mut runner = task::Scheduler::new(vec![ diff --git a/src/render.rs b/src/render.rs index cfc8eeb..bd877c2 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,21 +1,18 @@ -use embedded_graphics::{ - prelude::*, - pixelcolor::Rgb888 -}; -use ws2812_esp32_rmt_driver::lib_embedded_graphics::{Ws2812DrawTarget, LedPixelShape}; - use std::rc::Rc; use std::cell::RefCell; - -use running_average::RealTimeRunningAverage; +use std::sync::{Arc, Mutex}; +use palette::blend::{BlendWith, Equations, Parameter, PreAlpha}; 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, coords: Point) -> RGB8; + fn draw(&self, surface_coords: VirtualCoordinates) -> RGB8; } pub trait Surfaces { @@ -41,94 +38,41 @@ T: Display { } } -struct ShaderSlot { - shader: Option> +struct ShaderBinding { + shader: Option>, + opacity: u8, } +#[derive(Clone)] pub struct Surface { - slot: Rc> + pub binding: Arc> } impl Surface { - fn new(slot: Rc>) -> Self { + pub fn new() -> Self { Self { - slot: slot + binding: Arc::new(Mutex::new(ShaderBinding { + shader: None, + opacity: 255, + })), + } + } + + pub fn with_shader(&self, f: F) { + if let Some(ref shader) = self.binding.lock().unwrap().shader { + f(shader.as_ref()); } } pub fn set_shader(&mut self, shader: Box) { - self.slot.borrow_mut().shader = Some(shader); - } -} - -pub struct EmbeddedDisplay -where -T: DrawTarget { - shaders : RefCell>>>, - target: T, - total_mw: u32, - max_mw: u32, - fps: RealTimeRunningAverage, - frame: u32, - fps_display: Periodically -} - -impl EmbeddedDisplay -where -T: DrawTarget { - pub fn new(target: T, max_mw: u32) -> Self { - EmbeddedDisplay { - shaders: RefCell::new(Vec::new()), - target: target, - max_mw: max_mw, - total_mw: 0, - fps: RealTimeRunningAverage::default(), - frame: 0, - fps_display: Periodically::new_every_n_seconds(5) - } - } -} - -impl Surfaces for EmbeddedDisplay -where -T: DrawTarget { - fn new_surface(&mut self) -> Surface { - let slot = Rc::new(RefCell::new(ShaderSlot { - shader: None - })); - let surface = Surface::new(slot.clone()); - self.shaders.borrow_mut().push(slot); - return surface; - } -} - -impl Display for EmbeddedDisplay> { - fn start_frame(&mut self) { - self.target.clear(Rgb888::BLACK).unwrap(); - self.total_mw = 0; - self.frame = self.frame.wrapping_add(1); - } - - fn end_frame(&mut self) { - let brightness = power::brightness_for_mw(self.total_mw, 255, self.max_mw); - self.target.set_brightness(brightness); - self.target.flush().unwrap(); - self.fps.insert(1); - self.fps_display.run(|| { - log::info!("FPS: {} frame={} brightness={} mw={}", self.fps.measurement(), self.frame, brightness, self.total_mw); - }); - } - - fn render_frame(&mut self) { - for slot in self.shaders.borrow().iter() { - if let Some(ref shader) = slot.borrow().shader { - for i in 0..T::size().width { - let coords = Point::new(i as i32, 0); - let color = shader.draw(coords); - self.total_mw += power::color_to_mw(&color); - Pixel(coords, Rgb888::new(color.red, color.green, color.blue)).draw(&mut self.target).unwrap(); - } - } - } + self.binding.lock().unwrap().shader = Some(shader); + } + + pub fn clear_shader(&mut self) { + self.binding.lock().unwrap().shader = None; + } + + pub fn set_opacity(&mut self, opacity: u8) { + self.binding.lock().unwrap().opacity = opacity; } } diff --git a/src/task.rs b/src/task.rs index acd649e..29436cb 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::time::{Duration, Instant}; pub trait Task { fn tick(&mut self) {} @@ -82,7 +83,7 @@ impl ScheduledState for Stopped { struct ScheduledTask { state: Option>, - task: Box + task: Box, } impl std::fmt::Debug for ScheduledTask { @@ -98,7 +99,7 @@ impl ScheduledTask { fn new(task: Box) -> Self { ScheduledTask { state: Some(Box::new(Starting{})), - task: task + task: task, } } @@ -122,7 +123,7 @@ impl ScheduledTask { } pub struct Scheduler { - tasks: Vec + tasks: Vec, } impl Scheduler {