clean up imports and reorganize the ssd bits into a graphics mod
This commit is contained in:
241
src/graphics/display.rs
Normal file
241
src/graphics/display.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
|
||||
use figments::prelude::*;
|
||||
use core::{fmt::Debug, sync::atomic::{AtomicBool, AtomicU8}};
|
||||
use alloc::sync::Arc;
|
||||
|
||||
//use super::{Output};
|
||||
use figments_render::{
|
||||
gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, OutputAsync}, power::AsMilliwatts, smart_leds::PowerManagedWriterAsync
|
||||
};
|
||||
use smart_leds::SmartLedsWriteAsync;
|
||||
|
||||
pub const NUM_PIXELS: usize = 178;
|
||||
// FIXME: We need a way to specify a different buffer format from the 'native' hardware output, due to sometimes testing with GRB strips instead of RGB
|
||||
pub struct BikeOutput<T: SmartLedsWriteAsync> {
|
||||
pixbuf: [T::Color; NUM_PIXELS],
|
||||
writer: PowerManagedWriterAsync<T>,
|
||||
controls: DisplayControls
|
||||
}
|
||||
|
||||
impl<T:SmartLedsWriteAsync> GammaCorrected for BikeOutput<T> where T::Color: PixelBlend<Rgb<u8>> + PixelFormat + Debug + WithGamma + Default + Clone, T::Error: Debug {
|
||||
fn set_gamma(&mut self, gamma: GammaCurve) {
|
||||
self.writer.controls().set_gamma(gamma);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SmartLedsWriteAsync> BikeOutput<T> where T::Color: PixelBlend<Rgb<u8>> + PixelFormat + WithGamma + 'static + Default + Clone + Copy, T::Error: core::fmt::Debug {
|
||||
pub fn new(target: T, max_mw: u32, controls: DisplayControls) -> Self {
|
||||
Self {
|
||||
pixbuf: [Default::default(); NUM_PIXELS],
|
||||
writer: PowerManagedWriterAsync::new(target, max_mw),
|
||||
controls
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blank(&mut self) {
|
||||
self.pixbuf = [Default::default(); NUM_PIXELS];
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: SmartLedsWriteAsync + 'a> OutputAsync<'a, SegmentSpace> for BikeOutput<T> where T::Color: PixelSink<T::Color> + PixelBlend<Rgb<u8>> + Default + Clone + Debug + 'static + AsMilliwatts + PixelFormat + WithGamma, [T::Color; NUM_PIXELS]: AsMilliwatts + WithGamma + Copy, T::Error: core::fmt::Debug {
|
||||
async fn commit_async(&mut self) -> Result<(), T::Error> {
|
||||
self.writer.controls().set_brightness(self.controls.brightness());
|
||||
self.writer.controls().set_on(self.controls.is_on());
|
||||
// TODO: We should grab the power used here and somehow feed it back into the telemetry layer, probably just via another atomic u32
|
||||
self.writer.write(&self.pixbuf).await
|
||||
}
|
||||
|
||||
//type HardwarePixel = T::Color;
|
||||
type Error = T::Error;
|
||||
type Controls = DisplayControls;
|
||||
|
||||
fn controls(&self) -> Option<&Self::Controls> {
|
||||
Some(&self.controls)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: SmartLedsWriteAsync> Sample<'a, SegmentSpace> for BikeOutput<T> where T::Color: 'a + Debug + PixelFormat, [T::Color; NUM_PIXELS]: Sample<'a, SegmentSpace, Output = T::Color> {
|
||||
type Output = T::Color;
|
||||
|
||||
fn sample(&mut self, rect: &Rectangle<SegmentSpace>) -> impl Iterator<Item = (Coordinates<SegmentSpace>, &'a mut Self::Output)> {
|
||||
self.pixbuf.sample(rect)
|
||||
}
|
||||
}
|
||||
|
||||
const STRIP_MAP: [Segment; 6] = [
|
||||
Segment { start: 0, length: 28 },
|
||||
Segment { start: 28, length: 17 },
|
||||
Segment { start: 45, length: 14 },
|
||||
Segment { start: 59, length: 17 },
|
||||
Segment { start: 76, length: 14 },
|
||||
Segment { start: 90, length: 88 }
|
||||
];
|
||||
|
||||
pub struct Segment {
|
||||
start: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: PixelFormat> Sample<'a, SegmentSpace> for [T; NUM_PIXELS] where T: 'static {
|
||||
type Output = T;
|
||||
|
||||
fn sample(&mut self, rect: &Rectangle<SegmentSpace>) -> impl Iterator<Item = (Coordinates<SegmentSpace>, &'a mut Self::Output)> {
|
||||
let bufref = unsafe {
|
||||
&mut *(self as *mut [T; NUM_PIXELS])
|
||||
};
|
||||
SegmentIter {
|
||||
pixbuf: bufref,
|
||||
cur: rect.top_left,
|
||||
end: rect.bottom_right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct SegmentSpace {}
|
||||
impl CoordinateSpace for SegmentSpace {
|
||||
type Data = usize;
|
||||
}
|
||||
|
||||
pub struct SegmentIter<'a, Format: PixelFormat> {
|
||||
pixbuf: &'a mut [Format; NUM_PIXELS],
|
||||
cur: Coordinates<SegmentSpace>,
|
||||
end: Coordinates<SegmentSpace>,
|
||||
}
|
||||
|
||||
impl<'a, Format: PixelFormat + Debug> Debug for SegmentIter<'a, Format> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("BikeIter").field("cur", &self.cur).field("end", &self.end).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Format: PixelFormat> Iterator for SegmentIter<'a, Format> {
|
||||
type Item = (Coordinates<SegmentSpace>, &'a mut Format);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.cur.y > self.end.y || self.cur.y >= STRIP_MAP.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let this_strip = &STRIP_MAP[self.cur.y];
|
||||
|
||||
let offset = this_strip.start + self.cur.x;
|
||||
let pixel_coords = Coordinates::new(self.cur.x, self.cur.y);
|
||||
|
||||
self.cur.x += 1;
|
||||
|
||||
if self.cur.x >= this_strip.length || self.cur.x > self.end.x {
|
||||
self.cur.x = 0;
|
||||
self.cur.y += 1;
|
||||
}
|
||||
|
||||
let bufref = unsafe {
|
||||
&mut *(self.pixbuf as *mut [Format; NUM_PIXELS])
|
||||
};
|
||||
|
||||
Some((pixel_coords, &mut bufref[offset]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Uniforms {
|
||||
pub frame: usize,
|
||||
pub primary_color: Hsv
|
||||
}
|
||||
|
||||
struct ControlData {
|
||||
on: AtomicBool,
|
||||
brightness: AtomicU8
|
||||
}
|
||||
|
||||
impl Default for ControlData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on: AtomicBool::new(true),
|
||||
brightness: AtomicU8::new(255)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A watch that indicates whether or not the rendering engine is running. If the display is off or sleeping, this will be false.
|
||||
static RENDER_IS_RUNNING: Watch<CriticalSectionRawMutex, bool, 7> = Watch::new();
|
||||
|
||||
// TODO: Implement something similar for a system-wide sleep mechanism
|
||||
pub struct DisplayControls {
|
||||
data: Arc<ControlData>,
|
||||
render_pause: Arc<Signal<CriticalSectionRawMutex, bool>>,
|
||||
render_run_receiver: Receiver<'static, CriticalSectionRawMutex, bool, 7>
|
||||
}
|
||||
|
||||
impl Clone for DisplayControls {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: Arc::clone(&self.data),
|
||||
render_pause: Arc::clone(&self.render_pause),
|
||||
render_run_receiver: RENDER_IS_RUNNING.receiver().expect("Could not create enough render running receivers")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayControls {
|
||||
pub fn is_on(&self) -> bool {
|
||||
self.data.on.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn brightness(&self) -> u8 {
|
||||
self.data.brightness.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// FIXME: its a bit weird we have a pub function for the renderer's privates to wait while hiding render_pause, but directly expose render_is_running for any task to wait on
|
||||
pub async fn wait_until_display_is_on(&self) {
|
||||
if let Some(true) = self.render_pause.try_take() {
|
||||
while self.render_pause.wait().await {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_render_is_running(&mut self, value: bool) {
|
||||
RENDER_IS_RUNNING.sender().send(value);
|
||||
}
|
||||
|
||||
pub async fn wait_until_render_is_running(&mut self) {
|
||||
while !self.render_run_receiver.get().await {}
|
||||
}
|
||||
}
|
||||
|
||||
impl GammaCorrected for DisplayControls {
|
||||
fn set_gamma(&mut self, _gamma: GammaCurve) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Brightness for DisplayControls {
|
||||
fn set_brightness(&mut self, brightness: u8) {
|
||||
self.data.brightness.store(brightness, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set_on(&mut self, is_on: bool) {
|
||||
self.data.on.store(is_on, core::sync::atomic::Ordering::Relaxed);
|
||||
self.render_pause.signal(!is_on);
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for DisplayControls {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||
f.debug_struct("DisplayControls")
|
||||
.field("on", &self.data.on)
|
||||
.field("brightness", &self.data.brightness)
|
||||
.field("render_pause", &self.render_pause.signaled())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayControls {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data: Default::default(),
|
||||
render_pause: Default::default(),
|
||||
render_run_receiver: RENDER_IS_RUNNING.receiver().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user