graphics: display: implement dynamic target fps

This commit is contained in:
2026-03-09 10:16:02 +01:00
parent c77ecc9a19
commit f0d7968843
3 changed files with 32 additions and 11 deletions

View File

@@ -159,16 +159,21 @@ pub struct Uniforms {
pub primary_color: Hsv pub primary_color: Hsv
} }
pub const DEFAULT_FPS: u8 = 60;
pub const LOW_POWER_FPS: u8 = 16;
struct ControlData { struct ControlData {
on: AtomicBool, on: AtomicBool,
brightness: AtomicU8 brightness: AtomicU8,
fps: AtomicU8
} }
impl Default for ControlData { impl Default for ControlData {
fn default() -> Self { fn default() -> Self {
Self { Self {
on: AtomicBool::new(true), on: AtomicBool::new(true),
brightness: AtomicU8::new(255) brightness: AtomicU8::new(255),
fps: AtomicU8::new(DEFAULT_FPS)
} }
} }
} }
@@ -202,7 +207,15 @@ impl DisplayControls {
Fract8::from_raw(self.data.brightness.load(core::sync::atomic::Ordering::Relaxed)) Fract8::from_raw(self.data.brightness.load(core::sync::atomic::Ordering::Relaxed))
} }
pub async fn wait_until_display_is_on(&self) { pub fn fps(&self) -> u8 {
self.data.fps.load(core::sync::atomic::Ordering::Relaxed)
}
pub fn set_fps(&self, value: u8) {
self.data.fps.store(value, core::sync::atomic::Ordering::Relaxed);
}
pub async fn wait_until_display_is_turned_on(&self) {
while !self.display_is_on.wait().await { log::info!("wait for display") } while !self.display_is_on.wait().await { log::info!("wait for display") }
log::trace!("display says on!"); log::trace!("display says on!");
} }
@@ -241,6 +254,7 @@ impl core::fmt::Debug for DisplayControls {
f.debug_struct("DisplayControls") f.debug_struct("DisplayControls")
.field("on", &self.data.on) .field("on", &self.data.on)
.field("brightness", &self.data.brightness) .field("brightness", &self.data.brightness)
.field("fps", &self.data.fps)
.field("render_pause_signaled", &self.display_is_on.signaled()) .field("render_pause_signaled", &self.display_is_on.signaled())
.finish() .finish()
} }

View File

@@ -46,15 +46,13 @@ pub async fn render(spi: AnySpi<'static>, dma: AnyGdmaChannel<'static>, gpio: An
info!("Rendering started! {}ms since boot", Instant::now().as_millis()); info!("Rendering started! {}ms since boot", Instant::now().as_millis());
controls.notify_render_is_running(true); controls.notify_render_is_running(true);
// TODO: The prediction engine should be able to scale the display FPS down for power saving sometimes let mut requested_fps= controls.fps() as u64;
const FPS: u64 = 30; let mut render_budget = Duration::from_millis(1000 / requested_fps);
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
const ANIMATION_TPS: u64 = 120; const ANIMATION_TPS: u64 = 120;
const ANIMATION_FRAME_TIME: Duration = Duration::from_millis(1000 / ANIMATION_TPS); const ANIMATION_FRAME_TIME: Duration = Duration::from_millis(1000 / ANIMATION_TPS);
loop { loop {
// FIXME: need to put the rendering loop into a deep sleep when the display is off
let start = Instant::now(); let start = Instant::now();
output.blank(); output.blank();
@@ -70,13 +68,20 @@ pub async fn render(spi: AnySpi<'static>, dma: AnyGdmaChannel<'static>, gpio: An
let render_duration = Instant::now() - start; let render_duration = Instant::now() - start;
let next_fps = controls.fps() as u64;
if next_fps != requested_fps {
requested_fps = next_fps;
render_budget = Duration::from_millis(1000 / requested_fps);
info!("FPS changed to {requested_fps}");
}
if !controls.is_on() { if !controls.is_on() {
warn!("Renderer is sleeping zzzz"); warn!("Renderer is sleeping zzzz");
controls.notify_render_is_running(false); controls.notify_render_is_running(false);
output.blank(); output.blank();
output.commit_async().await.expect("Failed to commit low power frame"); output.commit_async().await.expect("Failed to commit low power frame");
wdt.disable(); wdt.disable();
controls.wait_until_display_is_on().await; controls.wait_until_display_is_turned_on().await;
wdt.feed(); wdt.feed();
wdt.enable(); wdt.enable();
warn!("Renderer is awake !!!!"); warn!("Renderer is awake !!!!");
@@ -84,12 +89,11 @@ pub async fn render(spi: AnySpi<'static>, dma: AnyGdmaChannel<'static>, gpio: An
} }
// Apply the FPS cap where we sleep if we are rendering fast enough // Apply the FPS cap where we sleep if we are rendering fast enough
if render_duration < RENDER_BUDGET { if render_duration < render_budget {
let remaining_budget = RENDER_BUDGET - render_duration; let remaining_budget = render_budget - render_duration;
Timer::after(remaining_budget).await; Timer::after(remaining_budget).await;
} else { } else {
// Otherwise, we have a problem // Otherwise, we have a problem
// TODO: Automatically scale FPS back when this happens
warn!("Render stall! Frame took {}ms", render_duration.as_millis()); warn!("Render stall! Frame took {}ms", render_duration.as_millis());
// Kick the scheduler since it took so long // Kick the scheduler since it took so long
Timer::after_ticks(1).await; Timer::after_ticks(1).await;

View File

@@ -94,6 +94,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
if let Prediction::SetPersonality(personality) = event { match personality { if let Prediction::SetPersonality(personality) = event { match personality {
Personality::Active => { Personality::Active => {
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake. // FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
self.display.set_fps(DEFAULT_FPS);
warn!("Active personality: Turning on safety lights"); warn!("Active personality: Turning on safety lights");
TURN_ON_FAST.apply([ TURN_ON_FAST.apply([
&mut self.brakelight, &mut self.brakelight,
@@ -106,6 +107,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
&mut self.brakelight, &mut self.brakelight,
&mut self.headlight &mut self.headlight
]).await; ]).await;
self.display.set_fps(LOW_POWER_FPS);
}, },
Personality::Sleeping => { Personality::Sleeping => {
warn!("Sleeping personality: Safety UI is going to sleep"); warn!("Sleeping personality: Safety UI is going to sleep");
@@ -113,6 +115,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
}, },
Personality::Waking => { Personality::Waking => {
warn!("Waking personality: Waking up safety UI"); warn!("Waking personality: Waking up safety UI");
self.display.set_fps(DEFAULT_FPS);
self.wake().await; self.wake().await;
}, },
} } } }