graphics: display: implement dynamic target fps
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
} }
|
} }
|
||||||
|
|||||||
Reference in New Issue
Block a user