kms: skip cursor updates for fullscreen content above the minimum refresh rate
This commit is contained in:
parent
adcb81bbe0
commit
b5cd62fd7a
3 changed files with 111 additions and 16 deletions
|
|
@ -239,6 +239,26 @@ pub fn calculate_refresh_rate(mode: Mode) -> u32 {
|
|||
refresh as u32
|
||||
}
|
||||
|
||||
pub fn get_minimum_refresh_rate(
|
||||
device: &impl ControlDevice,
|
||||
connector: connector::Handle,
|
||||
) -> Result<Option<u32>> {
|
||||
let info = edid_info(device, connector)?;
|
||||
let edid = info.edid().context("EDID lacking into")?;
|
||||
for descriptor in edid.display_descriptors() {
|
||||
if descriptor.tag() == DisplayDescriptorTag::RangeLimits {
|
||||
return Ok(Some(
|
||||
descriptor
|
||||
.range_limits()
|
||||
.context("Invalid range limits descriptor")?
|
||||
.min_vert_rate_hz as u32,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_max_bpc(
|
||||
dev: &impl ControlDevice,
|
||||
conn: connector::Handle,
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ fn surface_thread(
|
|||
vrr_mode: AdaptiveSync::Disabled,
|
||||
|
||||
state: QueueState::Idle,
|
||||
timings: Timings::new(None, false),
|
||||
timings: Timings::new(None, None, false),
|
||||
frame_callback_seq: 0,
|
||||
thread_sender,
|
||||
|
||||
|
|
@ -667,11 +667,28 @@ impl SurfaceThreadState {
|
|||
}
|
||||
|
||||
fn resume(&mut self, compositor: GbmDrmOutput) -> Result<()> {
|
||||
let mode = compositor.with_compositor(|c| c.surface().pending_mode());
|
||||
self.timings
|
||||
.set_refresh_interval(Some(Duration::from_secs_f64(
|
||||
1_000.0 / drm_helpers::calculate_refresh_rate(mode) as f64,
|
||||
)));
|
||||
let (mode, min_hz) = compositor.with_compositor(|c| {
|
||||
(
|
||||
c.surface().pending_mode(),
|
||||
drm_helpers::get_minimum_refresh_rate(
|
||||
c.surface(),
|
||||
c.pending_connectors().into_iter().next().unwrap(),
|
||||
)
|
||||
.ok()
|
||||
.flatten(),
|
||||
)
|
||||
});
|
||||
let interval =
|
||||
Duration::from_secs_f64(1_000. / drm_helpers::calculate_refresh_rate(mode) as f64);
|
||||
self.timings.set_refresh_interval(Some(interval));
|
||||
|
||||
let min_min_refresh_interval = Duration::from_secs_f64(1. / 30.); // 30Hz
|
||||
self.timings.set_min_refresh_interval(Some(
|
||||
min_hz
|
||||
.map(|min| Duration::from_secs_f64(1. / min as f64))
|
||||
.unwrap_or(min_min_refresh_interval) // alternatively use 30Hz
|
||||
.max(min_min_refresh_interval),
|
||||
));
|
||||
|
||||
if crate::utils::env::bool_var("COSMIC_DISABLE_DIRECT_SCANOUT").unwrap_or(false) {
|
||||
self.frame_flags.remove(FrameFlags::ALLOW_SCANOUT);
|
||||
|
|
@ -799,7 +816,7 @@ impl SurfaceThreadState {
|
|||
}
|
||||
}
|
||||
|
||||
fn on_estimated_vblank(&mut self) {
|
||||
fn on_estimated_vblank(&mut self, force: bool) {
|
||||
match mem::replace(&mut self.state, QueueState::Idle) {
|
||||
QueueState::Idle => unreachable!(),
|
||||
QueueState::Queued(_) => unreachable!(),
|
||||
|
|
@ -814,7 +831,7 @@ impl SurfaceThreadState {
|
|||
|
||||
self.frame_callback_seq = self.frame_callback_seq.wrapping_add(1);
|
||||
|
||||
if self.shell.read().unwrap().animations_going() {
|
||||
if force || self.shell.read().unwrap().animations_going() {
|
||||
self.queue_redraw(false);
|
||||
} else {
|
||||
self.send_frame_callbacks();
|
||||
|
|
@ -924,10 +941,14 @@ impl SurfaceThreadState {
|
|||
_ => false,
|
||||
};
|
||||
|
||||
if self.vrr_mode == AdaptiveSync::Enabled {
|
||||
let has_active_fullscreen = {
|
||||
let shell = self.shell.read().unwrap();
|
||||
let output = self.mirroring.as_ref().unwrap_or(&self.output);
|
||||
vrr = shell.workspaces.active(output).1.get_fullscreen().is_some();
|
||||
shell.workspaces.active(output).1.get_fullscreen().is_some()
|
||||
};
|
||||
|
||||
if self.vrr_mode == AdaptiveSync::Enabled {
|
||||
vrr = has_active_fullscreen;
|
||||
}
|
||||
|
||||
let mut elements = output_elements(
|
||||
|
|
@ -945,6 +966,14 @@ impl SurfaceThreadState {
|
|||
.map_err(|err| {
|
||||
anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err)
|
||||
})?;
|
||||
let additional_frame_flags = if vrr
|
||||
&& has_active_fullscreen
|
||||
&& !self.timings.past_min_presentation_time(&self.clock)
|
||||
{
|
||||
FrameFlags::SKIP_CURSOR_ONLY_UPDATES
|
||||
} else {
|
||||
FrameFlags::empty()
|
||||
};
|
||||
self.timings.set_vrr(vrr);
|
||||
self.timings.elements_done(&self.clock);
|
||||
|
||||
|
|
@ -1108,7 +1137,7 @@ impl SurfaceThreadState {
|
|||
&mut renderer,
|
||||
&elements,
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
self.frame_flags,
|
||||
self.frame_flags.union(additional_frame_flags),
|
||||
)
|
||||
} else {
|
||||
if let Err(err) = compositor.with_compositor(|c| c.use_vrr(vrr)) {
|
||||
|
|
@ -1118,7 +1147,7 @@ impl SurfaceThreadState {
|
|||
&mut renderer,
|
||||
&elements,
|
||||
CLEAR_COLOR, // TODO use a theme neutral color
|
||||
self.frame_flags,
|
||||
self.frame_flags.union(additional_frame_flags),
|
||||
)
|
||||
};
|
||||
self.timings.draw_done(&self.clock);
|
||||
|
|
@ -1321,7 +1350,12 @@ impl SurfaceThreadState {
|
|||
self.send_frame_callbacks();
|
||||
}
|
||||
} else {
|
||||
self.queue_estimated_vblank(estimated_presentation);
|
||||
self.queue_estimated_vblank(
|
||||
estimated_presentation,
|
||||
// Make sure we redraw to reevaluate, if we intentionally missed content
|
||||
additional_frame_flags
|
||||
.contains(FrameFlags::SKIP_CURSOR_ONLY_UPDATES),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
@ -1348,7 +1382,7 @@ impl SurfaceThreadState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn queue_estimated_vblank(&mut self, target_presentation_time: Duration) {
|
||||
fn queue_estimated_vblank(&mut self, target_presentation_time: Duration, force: bool) {
|
||||
match mem::take(&mut self.state) {
|
||||
QueueState::Idle => unreachable!(),
|
||||
QueueState::Queued(_) => (),
|
||||
|
|
@ -1378,7 +1412,7 @@ impl SurfaceThreadState {
|
|||
let token = self
|
||||
.loop_handle
|
||||
.insert_source(timer, move |_, _, data| {
|
||||
data.on_estimated_vblank();
|
||||
data.on_estimated_vblank(force);
|
||||
TimeoutAction::Drop
|
||||
})
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const FRAME_TIME_WINDOW: usize = 3;
|
|||
|
||||
pub struct Timings {
|
||||
refresh_interval_ns: Option<NonZeroU64>,
|
||||
min_refresh_interval_ns: Option<NonZeroU64>,
|
||||
vrr: bool,
|
||||
|
||||
pub pending_frame: Option<PendingFrame>,
|
||||
|
|
@ -44,7 +45,11 @@ impl Frame {
|
|||
impl Timings {
|
||||
const WINDOW_SIZE: usize = 360;
|
||||
|
||||
pub fn new(refresh_interval: Option<Duration>, vrr: bool) -> Self {
|
||||
pub fn new(
|
||||
refresh_interval: Option<Duration>,
|
||||
min_interval: Option<Duration>,
|
||||
vrr: bool,
|
||||
) -> Self {
|
||||
let refresh_interval_ns = if let Some(interval) = &refresh_interval {
|
||||
assert_eq!(interval.as_secs(), 0);
|
||||
Some(NonZeroU64::new(interval.subsec_nanos().into()).unwrap())
|
||||
|
|
@ -52,8 +57,16 @@ impl Timings {
|
|||
None
|
||||
};
|
||||
|
||||
let min_refresh_interval_ns = if let Some(interval) = &min_interval {
|
||||
assert_eq!(interval.as_secs(), 0);
|
||||
Some(NonZeroU64::new(interval.subsec_nanos().into()).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
refresh_interval_ns,
|
||||
min_refresh_interval_ns,
|
||||
vrr,
|
||||
|
||||
pending_frame: None,
|
||||
|
|
@ -76,6 +89,12 @@ impl Timings {
|
|||
self.previous_frames.clear();
|
||||
}
|
||||
|
||||
pub fn set_min_refresh_interval(&mut self, min_interval: Option<Duration>) {
|
||||
self.min_refresh_interval_ns = min_interval
|
||||
.map(|duration| duration.subsec_nanos() as u64)
|
||||
.and_then(NonZeroU64::new);
|
||||
}
|
||||
|
||||
pub fn set_vrr(&mut self, vrr: bool) {
|
||||
self.vrr = vrr;
|
||||
}
|
||||
|
|
@ -255,6 +274,28 @@ impl Timings {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn past_min_presentation_time(&self, clock: &Clock<Monotonic>) -> bool {
|
||||
let now: Duration = clock.now().into();
|
||||
let Some(refresh_interval_ns) = self.min_refresh_interval_ns else {
|
||||
return true;
|
||||
};
|
||||
let Some(last_presentation_time): Option<Duration> = self
|
||||
.previous_frames
|
||||
.back()
|
||||
.map(|frame| frame.presentation_presented.into())
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let refresh_interval_ns = refresh_interval_ns.get();
|
||||
if now <= last_presentation_time {
|
||||
return false;
|
||||
}
|
||||
|
||||
let next = last_presentation_time + Duration::from_nanos(refresh_interval_ns);
|
||||
now >= next
|
||||
}
|
||||
|
||||
pub fn next_render_time(&self, clock: &Clock<Monotonic>) -> Duration {
|
||||
let estimated_presentation_time = self.next_presentation_time(clock);
|
||||
if estimated_presentation_time.is_zero() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue