Separate requested and applied output runtime power
This commit is contained in:
1 parent
2f63cae54c
commit
bb78801720
3 files changed
+134
-14
No files matched your search
@@ -6,9 +6,11 @@ use std::path::Path;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
mod capture;
|
mod capture;
|
||||||
|
mod output_power;
|
||||||
mod planes;
|
mod planes;
|
||||||
mod redraw;
|
mod redraw;
|
||||||
mod scheduling;
|
mod scheduling;
|
||||||
|
use output_power::{OutputPower, Power};
|
||||||
use redraw::{AnimationFallback, SchedulingMetrics};
|
use redraw::{AnimationFallback, SchedulingMetrics};
|
||||||
use scheduling::*;
|
use scheduling::*;
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::Fourcc;
|
||||||
@@ -190,7 +192,7 @@ struct DirectOutput {
|
|||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
scheduler: crate::frame_scheduler::FrameScheduler,
|
scheduler: crate::frame_scheduler::FrameScheduler,
|
||||||
render_timer: Option<RegistrationToken>,
|
render_timer: Option<RegistrationToken>,
|
||||||
power_off: bool,
|
power: OutputPower,
|
||||||
callback_timer: Option<RegistrationToken>,
|
callback_timer: Option<RegistrationToken>,
|
||||||
lock_frame_pending: bool,
|
lock_frame_pending: bool,
|
||||||
}
|
}
|
||||||
@@ -284,7 +286,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
output.primary_commit = None;
|
output.primary_commit = None;
|
||||||
output.capture_texture = None;
|
output.capture_texture = None;
|
||||||
output.frame_pending = false;
|
output.frame_pending = false;
|
||||||
output.power_off = false;
|
output.power.suspended();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -577,6 +579,7 @@ fn open_primary_device(
|
|||||||
&& retired
|
&& retired
|
||||||
{
|
{
|
||||||
output.frame_pending = false;
|
output.frame_pending = false;
|
||||||
|
output.power.presented();
|
||||||
if let DrmEventTime::Monotonic(timestamp) = metadata.time {
|
if let DrmEventTime::Monotonic(timestamp) = metadata.time {
|
||||||
let now = monotonic_now();
|
let now = monotonic_now();
|
||||||
if timestamp <= now + refresh_duration_from_mode(output.mode) {
|
if timestamp <= now + refresh_duration_from_mode(output.mode) {
|
||||||
@@ -609,6 +612,8 @@ fn open_primary_device(
|
|||||||
state.refresh_idle_inhibition();
|
state.refresh_idle_inhibition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_output_power(state);
|
||||||
|
|
||||||
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
||||||
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
||||||
let active = state.direct_backend.as_ref().is_some_and(|backend| backend.active);
|
let active = state.direct_backend.as_ref().is_some_and(|backend| backend.active);
|
||||||
@@ -662,25 +667,37 @@ pub(crate) fn sleep_locked_outputs(state: &mut Ferese) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
for device in backend.devices.values_mut() {
|
||||||
|
for output in device.outputs.values_mut() {
|
||||||
|
output.power.request(Power::Off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apply_output_power(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply runtime requests without changing output globals or workspace ownership.
|
||||||
|
fn apply_output_power(state: &mut Ferese) {
|
||||||
let Some(backend) = state.direct_backend.as_mut() else {
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
for device in backend.devices.values_mut().filter(|device| device.drm.is_active()) {
|
for device in backend.devices.values_mut().filter(|device| device.drm.is_active()) {
|
||||||
for output in device.outputs.values_mut() {
|
for output in device.outputs.values_mut() {
|
||||||
if output.power_off || output.frame_pending {
|
if !output.power.needs_power_off() || output.frame_pending {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match output.surface.clear() {
|
match output.surface.clear() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
cancel_output_timers(&state.loop_handle, output);
|
cancel_output_timers(&state.loop_handle, output);
|
||||||
output.power_off = true;
|
output.power.powered_off();
|
||||||
state.display_presentation.remove_output(&output.output);
|
state.display_presentation.remove_output(&output.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::warn!(%error, output = %output.output.name(), "could not sleep locked display")
|
tracing::warn!(%error, output = %output.output.name(), "could not apply output power-off request")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -693,9 +710,10 @@ pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
|
|||||||
if let Some(backend) = state.direct_backend.as_mut() {
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
for device in backend.devices.values_mut() {
|
for device in backend.devices.values_mut() {
|
||||||
for output in device.outputs.values_mut() {
|
for output in device.outputs.values_mut() {
|
||||||
if std::mem::take(&mut output.power_off) {
|
if output.power.needs_wake() {
|
||||||
output.surface.reset_buffer_ages();
|
output.surface.reset_buffer_ages();
|
||||||
}
|
}
|
||||||
|
output.power.request(Power::On);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,7 +910,7 @@ fn render_output(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|backend| backend.devices.get(&node))
|
.and_then(|backend| backend.devices.get(&node))
|
||||||
.and_then(|device| device.outputs.get(&crtc))
|
.and_then(|device| device.outputs.get(&crtc))
|
||||||
.is_none_or(|output| output.power_off || output.frame_pending);
|
.is_none_or(|output| !output.power.can_render() || output.frame_pending);
|
||||||
if asleep {
|
if asleep {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -917,7 +935,7 @@ fn render_output(
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !device.drm.is_active() || output.frame_pending {
|
if !device.drm.is_active() || !output.power.can_render() || output.frame_pending {
|
||||||
device.outputs.insert(crtc, output);
|
device.outputs.insert(crtc, output);
|
||||||
restore_device(state, node, device);
|
restore_device(state, node, device);
|
||||||
return false;
|
return false;
|
||||||
@@ -1367,7 +1385,7 @@ fn animation_fallbacks(state: &mut Ferese) -> Vec<(DrmNode, crtc::Handle, Instan
|
|||||||
if let Some(deadline) = output.animation_fallback.deadline(
|
if let Some(deadline) = output.animation_fallback.deadline(
|
||||||
now,
|
now,
|
||||||
refresh_duration_from_mode(output.mode),
|
refresh_duration_from_mode(output.mode),
|
||||||
active && !output.power_off,
|
active && output.power.can_render(),
|
||||||
chained,
|
chained,
|
||||||
) {
|
) {
|
||||||
fallbacks.push((node, crtc, deadline));
|
fallbacks.push((node, crtc, deadline));
|
||||||
@@ -1518,7 +1536,12 @@ fn deliver_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle
|
|||||||
};
|
};
|
||||||
|
|
||||||
output.callback_timer = None;
|
output.callback_timer = None;
|
||||||
if !backend.active || !device.drm.is_active() || state.session_lock.sleeping || output.frame_pending {
|
if !backend.active
|
||||||
|
|| !device.drm.is_active()
|
||||||
|
|| !output.power.can_render()
|
||||||
|
|| state.session_lock.sleeping
|
||||||
|
|| output.frame_pending
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1804,7 +1827,7 @@ fn create_direct_output(
|
|||||||
frame_pending: false,
|
frame_pending: false,
|
||||||
animation_fallback: AnimationFallback::default(),
|
animation_fallback: AnimationFallback::default(),
|
||||||
cursor_visible: false,
|
cursor_visible: false,
|
||||||
power_off: false,
|
power: OutputPower::default(),
|
||||||
scheduler: crate::frame_scheduler::FrameScheduler::new(refresh_duration_from_mode(mode)),
|
scheduler: crate::frame_scheduler::FrameScheduler::new(refresh_duration_from_mode(mode)),
|
||||||
render_timer: None,
|
render_timer: None,
|
||||||
callback_timer: None,
|
callback_timer: None,
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
//! Runtime power is independent of connector presence and configured enablement.
|
||||||
|
//! Only successful hardware transitions change the applied state.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum Power {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct OutputPower {
|
||||||
|
requested: Power,
|
||||||
|
applied: Option<Power>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OutputPower {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
requested: Power::On,
|
||||||
|
applied: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputPower {
|
||||||
|
pub fn request(&mut self, power: Power) {
|
||||||
|
self.requested = power;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_render(&self) -> bool {
|
||||||
|
self.requested == Power::On
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_power_off(&self) -> bool {
|
||||||
|
self.requested == Power::Off && self.applied != Some(Power::Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_wake(&self) -> bool {
|
||||||
|
self.requested == Power::Off || self.applied != Some(Power::On)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn powered_off(&mut self) {
|
||||||
|
self.applied = Some(Power::Off);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn presented(&mut self) {
|
||||||
|
self.applied = Some(Power::On);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suspended(&mut self) {
|
||||||
|
self.applied = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_power_off_keeps_the_applied_state_and_remains_retryable() {
|
||||||
|
let mut power = OutputPower::default();
|
||||||
|
power.presented();
|
||||||
|
power.request(Power::Off);
|
||||||
|
assert!(!power.can_render());
|
||||||
|
assert_eq!(power.applied, Some(Power::On));
|
||||||
|
assert!(power.needs_power_off());
|
||||||
|
power.powered_off();
|
||||||
|
assert!(!power.needs_power_off());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn waking_does_not_claim_success_before_presentation() {
|
||||||
|
let mut power = OutputPower::default();
|
||||||
|
power.request(Power::Off);
|
||||||
|
power.powered_off();
|
||||||
|
power.request(Power::On);
|
||||||
|
assert!(power.can_render());
|
||||||
|
assert_eq!(power.applied, Some(Power::Off));
|
||||||
|
power.presented();
|
||||||
|
assert!(!power.needs_wake());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pending_frame_retirement_and_suspend_do_not_lose_requested_power() {
|
||||||
|
let mut power = OutputPower::default();
|
||||||
|
power.request(Power::Off);
|
||||||
|
power.presented();
|
||||||
|
assert!(power.needs_power_off());
|
||||||
|
power.suspended();
|
||||||
|
assert_eq!(power.applied, None);
|
||||||
|
assert!(power.needs_power_off());
|
||||||
|
assert!(!power.can_render());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ pub(super) fn request_frame(state: &mut Ferese, node: DrmNode, crtc: crtc::Handl
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if output.power_off {
|
if !output.power.can_render() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,11 @@ pub(super) fn arm_frame(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if output.render_timer.is_some() || output.frame_pending || output.power_off || state.session_lock.sleeping {
|
if output.render_timer.is_some()
|
||||||
|
|| output.frame_pending
|
||||||
|
|| !output.power.can_render()
|
||||||
|
|| state.session_lock.sleeping
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +114,7 @@ fn dispatch_frame(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle, identit
|
|||||||
};
|
};
|
||||||
|
|
||||||
output.render_timer = None;
|
output.render_timer = None;
|
||||||
if output.frame_pending || output.power_off || state.session_lock.sleeping {
|
if output.frame_pending || !output.power.can_render() || state.session_lock.sleeping {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user