diff --git a/compositor/src/backends/direct.rs b/compositor/src/backends/direct.rs index 3320f93..39b1658 100644 --- a/compositor/src/backends/direct.rs +++ b/compositor/src/backends/direct.rs @@ -862,6 +862,10 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle, plan: cr let submitted = rendered.as_ref().is_ok_and(|submitted| *submitted); record_frame_schedule(&mut output, plan, render_started.elapsed(), submitted); + if submitted && let Some(token) = output.callback_timer.take() { + forecast.loop_handle.remove(token); + } + match rendered { Ok(true) => tracing::trace!(?node, ?crtc, "queued DRM frame"), Ok(false) => (), @@ -1262,12 +1266,15 @@ fn schedule_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handl return; }; - if output.callback_timer.is_some() { + if output.callback_timer.is_some() || output.frame_pending { return; } let identity = output.output.clone(); - let callback_at = output.scheduler.callback_deadline(timestamp, target); + let Some(callback_at) = output.scheduler.callback_deadline(timestamp, target) else { + return; + }; + if callback_at <= timestamp { deliver_frame_callbacks(state, node, crtc, &identity); return; @@ -1313,12 +1320,15 @@ fn deliver_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle }; output.callback_timer = None; - if !backend.active || !device.drm.is_active() || state.session_lock.sleeping { + if !backend.active || !device.drm.is_active() || state.session_lock.sleeping || output.frame_pending { return; } let timestamp = monotonic_now(); - let next = output.scheduler.callback_deadline(timestamp, timestamp); + let Some(next) = output.scheduler.callback_deadline(timestamp, timestamp) else { + return; + }; + if next > timestamp { schedule_frame_callbacks(state, node, crtc, next); return; diff --git a/compositor/src/frame_scheduler.rs b/compositor/src/frame_scheduler.rs index c4a19e7..b4c124c 100644 --- a/compositor/src/frame_scheduler.rs +++ b/compositor/src/frame_scheduler.rs @@ -192,14 +192,20 @@ impl FrameScheduler { }) } - pub fn callback_deadline(&self, now: Duration, target: Duration) -> Duration { + pub fn callback_deadline(&self, now: Duration, target: Duration) -> Option { + // A timer from a callback-only update must yield to a later submission. + // Its page flip will release callbacks using the observed refresh phase. + if self.submitted.is_some() { + return None; + } + let tolerance = Duration::from_micros(500).min(self.interval / 8); if target <= now && self .last_callback .is_none_or(|last| last + self.interval <= now + tolerance) { - return now; + return Some(now); } let earliest = self @@ -207,7 +213,7 @@ impl FrameScheduler { .map_or(now, |last| last + self.interval) .max(target) .max(now); - self.phase_at_or_after(earliest) + Some(self.phase_at_or_after(earliest)) } pub fn callback_sent(&mut self, now: Duration) { @@ -298,7 +304,35 @@ mod tests { scheduler.callback_sent(ms(110)); scheduler.request(ms(108)); assert_eq!(scheduler.plan(ms(108)).unwrap().present_at, ms(120)); - assert_eq!(scheduler.callback_deadline(ms(111), ms(110)), ms(120)); + assert_eq!(scheduler.callback_deadline(ms(111), ms(110)).unwrap(), ms(120)); + } + + #[test] + fn callback_only_timer_waits_for_a_later_submission_even_when_it_misses_its_target() { + let mut scheduler = FrameScheduler::new(ms(10)); + scheduler.presented(ms(100)); + scheduler.request(ms(101)); + let unchanged = scheduler.plan(ms(101)).unwrap(); + scheduler.begin(); + scheduler.rendered(unchanged, ms(108), ms(1), false); + let callback_at = scheduler.callback_deadline(ms(108), unchanged.present_at).unwrap(); + assert_eq!(callback_at, ms(110)); + + scheduler.request(ms(109)); + let changed = scheduler.plan(ms(109)).unwrap(); + scheduler.begin(); + scheduler.rendered(changed, ms(118), ms(1), true); + + // The old timer is dispatched late, after a new frame was queued. + // Neither that timer nor the predicted refresh can release callbacks. + assert_eq!(scheduler.callback_deadline(ms(118), callback_at), None); + assert_eq!(scheduler.callback_deadline(ms(120), changed.present_at), None); + assert_eq!(scheduler.callback_deadline(ms(129), callback_at), None); + + scheduler.presented(ms(130)); + assert_eq!(scheduler.callback_deadline(ms(130), ms(130)), Some(ms(130))); + scheduler.callback_sent(ms(130)); + assert_eq!(scheduler.callback_deadline(ms(130), callback_at), Some(ms(140))); } #[test] @@ -323,9 +357,9 @@ mod tests { scheduler.callback_sent(ms(100) + Duration::from_micros(80)); scheduler.presented(ms(110)); let now = ms(110) + Duration::from_micros(10); - assert_eq!(scheduler.callback_deadline(now, now), now); + assert_eq!(scheduler.callback_deadline(now, now).unwrap(), now); scheduler.callback_sent(now); - assert_eq!(scheduler.callback_deadline(now, now), ms(120)); + assert_eq!(scheduler.callback_deadline(now, now).unwrap(), ms(120)); } #[test] diff --git a/compositor/src/presentation.rs b/compositor/src/presentation.rs index 34a8bec..ee25513 100644 --- a/compositor/src/presentation.rs +++ b/compositor/src/presentation.rs @@ -430,19 +430,21 @@ mod tests { let start = Duration::ZERO; let refresh = Duration::from_millis(16); let mut clock = crate::frame_scheduler::FrameScheduler::new(refresh); - assert_eq!(clock.callback_deadline(start, start), start); + assert_eq!(clock.callback_deadline(start, start).unwrap(), start); clock.callback_sent(start); for millisecond in 1..16 { assert_eq!( - clock.callback_deadline(start + Duration::from_millis(millisecond), start), + clock + .callback_deadline(start + Duration::from_millis(millisecond), start) + .unwrap(), start + refresh ); } let late = start + Duration::from_millis(50); - assert_eq!(clock.callback_deadline(late, late), late); + assert_eq!(clock.callback_deadline(late, late).unwrap(), late); clock.callback_sent(late); - assert_eq!(clock.callback_deadline(late, late), late + refresh); + assert_eq!(clock.callback_deadline(late, late).unwrap(), late + refresh); } #[test] @@ -454,11 +456,11 @@ mod tests { fast.callback_sent(start); fast.callback_sent(start + Duration::from_millis(8)); assert_eq!( - slow.callback_deadline(start + Duration::from_millis(8), start), + slow.callback_deadline(start + Duration::from_millis(8), start).unwrap(), start + Duration::from_millis(16) ); assert_eq!( - fast.callback_deadline(start + Duration::from_millis(8), start), + fast.callback_deadline(start + Duration::from_millis(8), start).unwrap(), start + Duration::from_millis(16) ); } diff --git a/compositor/src/render/decorations.rs b/compositor/src/render/decorations.rs index 1476cbe..6f7fbab 100644 --- a/compositor/src/render/decorations.rs +++ b/compositor/src/render/decorations.rs @@ -29,11 +29,11 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender UniformName::new("opacity", UniformType::_1f), UniformName::new("shadow_color", UniformType::_4f), ]; - let texture = renderer.compile_custom_texture_shader(&corner_shader(ROUNDED_TEXTURE_SHADER), &texture_uniforms); - let border = renderer.compile_custom_pixel_shader(&corner_shader(ROUNDED_BORDER_SHADER), &border_uniforms); - let shadow = renderer.compile_custom_pixel_shader(&corner_shader(WINDOW_SHADOW_SHADER), &shadow_uniforms); + let texture = renderer.compile_custom_texture_shader(corner_shader(ROUNDED_TEXTURE_SHADER), &texture_uniforms); + let border = renderer.compile_custom_pixel_shader(corner_shader(ROUNDED_BORDER_SHADER), &border_uniforms); + let shadow = renderer.compile_custom_pixel_shader(corner_shader(WINDOW_SHADOW_SHADER), &shadow_uniforms); let solid = renderer.compile_custom_pixel_shader( - &corner_shader(ROUNDED_SOLID_SHADER), + corner_shader(ROUNDED_SOLID_SHADER), &[ UniformName::new("clip_rect", UniformType::_4f), UniformName::new("radius", UniformType::_1f), diff --git a/compositor/src/render/materials.rs b/compositor/src/render/materials.rs index c04f352..0bf912e 100644 --- a/compositor/src/render/materials.rs +++ b/compositor/src/render/materials.rs @@ -322,7 +322,7 @@ pub(super) fn blur_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> O if let Some(program) = state.blur_programs.get(&context) { return Some(program.clone()); } - match renderer.compile_custom_texture_shader(&corner_shader(BLUR_SHADER), &blur_uniform_names()) { + match renderer.compile_custom_texture_shader(corner_shader(BLUR_SHADER), &blur_uniform_names()) { Ok(program) => { let program = BlurProgram(program); state.blur_programs.insert(context, program.clone()); @@ -394,7 +394,7 @@ pub(super) fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer) UniformName::new("shadow_rect", UniformType::_4f), UniformName::new("shadow_values", UniformType::_2f), ]; - match renderer.compile_custom_pixel_shader(&corner_shader(MATERIAL_SHADER), &uniforms) { + match renderer.compile_custom_pixel_shader(corner_shader(MATERIAL_SHADER), &uniforms) { Ok(program) => { let program = MaterialProgram(program); state.material_programs.insert(context, program.clone()); diff --git a/compositor/src/render/mod.rs b/compositor/src/render/mod.rs index 5a79146..00ddeec 100644 --- a/compositor/src/render/mod.rs +++ b/compositor/src/render/mod.rs @@ -542,7 +542,7 @@ mod tests { let mut renderer = unsafe { GlesRenderer::new(context).unwrap() }; let program = renderer .compile_custom_pixel_shader( - &super::corner_shader(super::ROUNDED_SOLID_SHADER), + super::corner_shader(super::ROUNDED_SOLID_SHADER), &[ UniformName::new("clip_rect", UniformType::_4f), UniformName::new("radius", UniformType::_1f), @@ -599,7 +599,7 @@ mod tests { let mut renderer = unsafe { GlesRenderer::new(context).unwrap() }; for source in [super::ROUNDED_TEXTURE_SHADER, super::BLUR_SHADER] { renderer - .compile_custom_texture_shader(&super::corner_shader(source), &[]) + .compile_custom_texture_shader(super::corner_shader(source), &[]) .unwrap(); } for source in [ @@ -609,7 +609,7 @@ mod tests { super::MATERIAL_SHADER, ] { renderer - .compile_custom_pixel_shader(&super::corner_shader(source), &[]) + .compile_custom_pixel_shader(super::corner_shader(source), &[]) .unwrap(); } } @@ -627,7 +627,7 @@ mod tests { let context = EGLContext::new(&display).unwrap(); let mut renderer = unsafe { GlesRenderer::new(context).unwrap() }; let program = renderer - .compile_custom_texture_shader(&super::corner_shader(super::BLUR_SHADER), &super::blur_uniform_names()) + .compile_custom_texture_shader(super::corner_shader(super::BLUR_SHADER), &super::blur_uniform_names()) .unwrap(); let size = (32, 32).into(); let mut pixels = vec![128u8; 32 * 32 * 4]; diff --git a/shell/ferese-shell/src/surfaces.rs b/shell/ferese-shell/src/surfaces.rs index 100644d..2f58bc3 100644 --- a/shell/ferese-shell/src/surfaces.rs +++ b/shell/ferese-shell/src/surfaces.rs @@ -562,8 +562,10 @@ mod region_encoding_tests { let regions = [[0.25, -0.75, 100.5, 40.25, 14.0]]; let bytes = super::encode_regions(®ions); let decoded: Vec<_> = bytes - .chunks_exact(4) - .map(|v| f32::from_ne_bytes(v.try_into().unwrap())) + .as_chunks::<4>() + .0 + .iter() + .map(|&v| f32::from_ne_bytes(v)) .collect(); assert_eq!(decoded, regions[0]); }