Reduce polling retries and repeated compositor lookups
This commit is contained in:
1 parent
dcca5fec88
commit
917379d548
4 files changed
+146
-24
No files matched your search
@@ -1,4 +1,7 @@
|
|||||||
use std::time::{Duration, Instant};
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use smithay::utils::{Physical, Rectangle};
|
use smithay::utils::{Physical, Rectangle};
|
||||||
|
|
||||||
@@ -16,7 +19,7 @@ pub(crate) struct RenderMetrics {
|
|||||||
damaged_pixels: u64,
|
damaged_pixels: u64,
|
||||||
total_render_time: Duration,
|
total_render_time: Duration,
|
||||||
longest_render_time: Duration,
|
longest_render_time: Duration,
|
||||||
frame_times: Vec<Duration>,
|
frame_times: VecDeque<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderMetrics {
|
impl RenderMetrics {
|
||||||
@@ -37,7 +40,7 @@ impl RenderMetrics {
|
|||||||
damaged_pixels: 0,
|
damaged_pixels: 0,
|
||||||
total_render_time: Duration::ZERO,
|
total_render_time: Duration::ZERO,
|
||||||
longest_render_time: Duration::ZERO,
|
longest_render_time: Duration::ZERO,
|
||||||
frame_times: Vec::new(),
|
frame_times: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +60,9 @@ impl RenderMetrics {
|
|||||||
self.total_render_time += render_time;
|
self.total_render_time += render_time;
|
||||||
self.longest_render_time = self.longest_render_time.max(render_time);
|
self.longest_render_time = self.longest_render_time.max(render_time);
|
||||||
if self.frame_times.len() == 512 {
|
if self.frame_times.len() == 512 {
|
||||||
self.frame_times.remove(0);
|
self.frame_times.pop_front();
|
||||||
}
|
}
|
||||||
self.frame_times.push(render_time);
|
self.frame_times.push_back(render_time);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let elapsed = now.saturating_duration_since(self.interval_started);
|
let elapsed = now.saturating_duration_since(self.interval_started);
|
||||||
@@ -72,7 +75,7 @@ impl RenderMetrics {
|
|||||||
.as_micros()
|
.as_micros()
|
||||||
.checked_div(u128::from(self.rendered_frames))
|
.checked_div(u128::from(self.rendered_frames))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
self.frame_times.sort_unstable();
|
self.frame_times.make_contiguous().sort_unstable();
|
||||||
let percentile = |percent: usize| {
|
let percentile = |percent: usize| {
|
||||||
let index = (self.frame_times.len() * percent)
|
let index = (self.frame_times.len() * percent)
|
||||||
.div_ceil(100)
|
.div_ceil(100)
|
||||||
@@ -159,4 +162,27 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(metrics.frame_times.len(), 512);
|
assert_eq!(metrics.frame_times.len(), 512);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profiling_retains_the_most_recent_samples_in_order() {
|
||||||
|
let mut metrics = RenderMetrics::new("test", true, Instant::now());
|
||||||
|
for micros in 0..600 {
|
||||||
|
metrics.record_frame(Duration::from_micros(micros), &[], 0, FrameEffectMetrics);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
metrics.frame_times.front(),
|
||||||
|
Some(&Duration::from_micros(88))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
metrics.frame_times.back(),
|
||||||
|
Some(&Duration::from_micros(599))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
metrics
|
||||||
|
.frame_times
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.eq((88..600).map(Duration::from_micros))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use ferese_layout::WindowId;
|
use ferese_layout::WindowId;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) -> u8 {
|
pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) -> u8 {
|
||||||
if fullscreen {
|
if fullscreen {
|
||||||
@@ -16,17 +17,24 @@ pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) ->
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct WindowStack {
|
pub(crate) struct WindowStack {
|
||||||
order: Vec<WindowId>,
|
order: Vec<WindowId>,
|
||||||
|
ranks: HashMap<WindowId, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowStack {
|
impl WindowStack {
|
||||||
pub fn insert(&mut self, id: WindowId) {
|
pub fn insert(&mut self, id: WindowId) {
|
||||||
if !self.order.contains(&id) {
|
if let std::collections::hash_map::Entry::Vacant(entry) = self.ranks.entry(id) {
|
||||||
|
entry.insert(self.order.len());
|
||||||
self.order.push(id);
|
self.order.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, id: WindowId) {
|
pub fn remove(&mut self, id: WindowId) {
|
||||||
self.order.retain(|candidate| *candidate != id);
|
if let Some(rank) = self.ranks.remove(&id) {
|
||||||
|
self.order.remove(rank);
|
||||||
|
for (rank, candidate) in self.order.iter().enumerate().skip(rank) {
|
||||||
|
self.ranks.insert(*candidate, rank);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raise(&mut self, id: WindowId) {
|
pub fn raise(&mut self, id: WindowId) {
|
||||||
@@ -35,10 +43,7 @@ impl WindowStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rank(&self, id: WindowId) -> usize {
|
pub fn rank(&self, id: WindowId) -> usize {
|
||||||
self.order
|
self.ranks.get(&id).copied().unwrap_or(usize::MAX)
|
||||||
.iter()
|
|
||||||
.position(|candidate| *candidate == id)
|
|
||||||
.unwrap_or(usize::MAX)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +51,43 @@ impl WindowStack {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cached_ranks_match_reference_order_after_mixed_operations() {
|
||||||
|
let mut stack = WindowStack::default();
|
||||||
|
let mut reference = Vec::new();
|
||||||
|
let mut random = 42_u64;
|
||||||
|
for _ in 0..2000 {
|
||||||
|
random = random.wrapping_mul(6364136223846793005).wrapping_add(1);
|
||||||
|
let id = WindowId((random >> 32) % 32);
|
||||||
|
match random % 3 {
|
||||||
|
0 => {
|
||||||
|
stack.insert(id);
|
||||||
|
if !reference.contains(&id) {
|
||||||
|
reference.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
stack.remove(id);
|
||||||
|
reference.retain(|candidate| *candidate != id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
stack.raise(id);
|
||||||
|
reference.retain(|candidate| *candidate != id);
|
||||||
|
reference.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in (0..33).map(WindowId) {
|
||||||
|
assert_eq!(
|
||||||
|
stack.rank(id),
|
||||||
|
reference
|
||||||
|
.iter()
|
||||||
|
.position(|candidate| *candidate == id)
|
||||||
|
.unwrap_or(usize::MAX)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn floating_windows_stay_above_tiled_and_maximized_windows() {
|
fn floating_windows_stay_above_tiled_and_maximized_windows() {
|
||||||
assert!(layer_priority(true, false, false) > layer_priority(false, true, false));
|
assert!(layer_priority(true, false, false) > layer_priority(false, true, false));
|
||||||
|
|||||||
@@ -1428,6 +1428,13 @@ fn overview_strip_elements(
|
|||||||
let cards = state.overview_workspace_cards(output);
|
let cards = state.overview_workspace_cards(output);
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
let programs = rounded_clip_program(state, renderer);
|
let programs = rounded_clip_program(state, renderer);
|
||||||
|
// Resolve IDs once per strip, rather than scanning all managed windows
|
||||||
|
// separately for every miniature. Own the handles so rendering can mutate state.
|
||||||
|
let windows_by_id: std::collections::HashMap<_, _> = state
|
||||||
|
.window_ids
|
||||||
|
.iter()
|
||||||
|
.map(|(window, id)| (*id, window.clone()))
|
||||||
|
.collect();
|
||||||
for card in cards {
|
for card in cards {
|
||||||
// Front-to-back: outline and live miniatures above each card, all above
|
// Front-to-back: outline and live miniatures above each card, all above
|
||||||
// the strip. Nothing is painted behind the main overview window grid.
|
// the strip. Nothing is painted behind the main overview window grid.
|
||||||
@@ -1451,14 +1458,10 @@ fn overview_strip_elements(
|
|||||||
}
|
}
|
||||||
if let Some(programs) = &programs {
|
if let Some(programs) = &programs {
|
||||||
for (id, rect) in &card.windows {
|
for (id, rect) in &card.windows {
|
||||||
if let Some(window) = state
|
if let Some(window) = windows_by_id.get(id) {
|
||||||
.window_ids
|
|
||||||
.iter()
|
|
||||||
.find_map(|(window, candidate)| (*candidate == *id).then(|| window.clone()))
|
|
||||||
{
|
|
||||||
elements.extend(rounded_window_elements(
|
elements.extend(rounded_window_elements(
|
||||||
renderer,
|
renderer,
|
||||||
&window,
|
window,
|
||||||
rounded_visual_rect(*rect, output_geometry.loc),
|
rounded_visual_rect(*rect, output_geometry.loc),
|
||||||
physical_rect(*rect, output_geometry.loc, scale),
|
physical_rect(*rect, output_geometry.loc, scale),
|
||||||
scale,
|
scale,
|
||||||
|
|||||||
@@ -90,6 +90,16 @@ pub struct Service {
|
|||||||
pub generation: u64,
|
pub generation: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PollState = (Mutex<(u64, bool, Option<String>, bool)>, Condvar);
|
||||||
|
|
||||||
|
fn wait_for_poll(shared: &PollState) -> Option<u64> {
|
||||||
|
let state = shared
|
||||||
|
.1
|
||||||
|
.wait_while(shared.0.lock().unwrap(), |state| state.1 && !state.3)
|
||||||
|
.unwrap();
|
||||||
|
(!state.3).then_some(state.0)
|
||||||
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn start(settings: Option<Vec<String>>) -> Self {
|
pub fn start(settings: Option<Vec<String>>) -> Self {
|
||||||
let live_settings = Arc::new(Mutex::new(settings));
|
let live_settings = Arc::new(Mutex::new(settings));
|
||||||
@@ -105,12 +115,11 @@ impl Service {
|
|||||||
let polling = shared.clone();
|
let polling = shared.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
let before = {
|
// A poll overlapping a write is discarded below. Wait for the
|
||||||
let state = polling.0.lock().unwrap();
|
// write to finish before retrying instead of launching commands
|
||||||
if state.3 {
|
// repeatedly while a slow control operation is still running.
|
||||||
break;
|
let Some(before) = wait_for_poll(&polling) else {
|
||||||
}
|
break;
|
||||||
state.0
|
|
||||||
};
|
};
|
||||||
let snapshot = poll();
|
let snapshot = poll();
|
||||||
let state = polling.0.lock().unwrap();
|
let state = polling.0.lock().unwrap();
|
||||||
@@ -476,6 +485,48 @@ fn execute(action: &Action, settings: Option<&[String]>) -> Result<(), String> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn polling_waits_for_a_write_and_uses_the_completed_generation() {
|
||||||
|
let shared = Arc::new((Mutex::new((7, true, None, false)), Condvar::new()));
|
||||||
|
let worker_state = shared.clone();
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let worker = thread::spawn(move || {
|
||||||
|
sender.send(wait_for_poll(&worker_state)).unwrap();
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
receiver.recv_timeout(Duration::from_millis(30)),
|
||||||
|
Err(mpsc::RecvTimeoutError::Timeout)
|
||||||
|
));
|
||||||
|
{
|
||||||
|
let mut state = shared.0.lock().unwrap();
|
||||||
|
state.0 = 8;
|
||||||
|
state.1 = false;
|
||||||
|
shared.1.notify_one();
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
|
||||||
|
Some(8)
|
||||||
|
);
|
||||||
|
worker.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shutdown_unblocks_polling_even_during_a_write() {
|
||||||
|
let shared = Arc::new((Mutex::new((7, true, None, false)), Condvar::new()));
|
||||||
|
let worker_state = shared.clone();
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let worker = thread::spawn(move || {
|
||||||
|
sender.send(wait_for_poll(&worker_state)).unwrap();
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let mut state = shared.0.lock().unwrap();
|
||||||
|
state.3 = true;
|
||||||
|
shared.1.notify_one();
|
||||||
|
}
|
||||||
|
assert_eq!(receiver.recv_timeout(Duration::from_secs(2)).unwrap(), None);
|
||||||
|
worker.join().unwrap();
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn audio_parser_handles_mute_and_rejects_invalid_values() {
|
fn audio_parser_handles_mute_and_rejects_invalid_values() {
|
||||||
assert_eq!(parse_audio("Volume: 0.42 [MUTED]"), Some((42, true)));
|
assert_eq!(parse_audio("Volume: 0.42 [MUTED]"), Some((42, true)));
|
||||||
|
|||||||
Reference in new issue
Block a user