Wait for DRM presentation before releasing frame callbacks
Cancel callback-only timers when a frame is submitted and defer callback delivery until retirement. Cover a delayed timer and missed presentation deadline in the scheduler tests. Clean up shader argument borrows and shell region decoding to pass Clippy.
This commit is contained in:
1 parent
a40b899b8c
commit
3d0c85c70a
7 files changed
+76
-28
No files matched your search
@@ -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);
|
let submitted = rendered.as_ref().is_ok_and(|submitted| *submitted);
|
||||||
record_frame_schedule(&mut output, plan, render_started.elapsed(), 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 {
|
match rendered {
|
||||||
Ok(true) => tracing::trace!(?node, ?crtc, "queued DRM frame"),
|
Ok(true) => tracing::trace!(?node, ?crtc, "queued DRM frame"),
|
||||||
Ok(false) => (),
|
Ok(false) => (),
|
||||||
@@ -1262,12 +1266,15 @@ fn schedule_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handl
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if output.callback_timer.is_some() {
|
if output.callback_timer.is_some() || output.frame_pending {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let identity = output.output.clone();
|
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 {
|
if callback_at <= timestamp {
|
||||||
deliver_frame_callbacks(state, node, crtc, &identity);
|
deliver_frame_callbacks(state, node, crtc, &identity);
|
||||||
return;
|
return;
|
||||||
@@ -1313,12 +1320,15 @@ 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 {
|
if !backend.active || !device.drm.is_active() || state.session_lock.sleeping || output.frame_pending {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = monotonic_now();
|
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 {
|
if next > timestamp {
|
||||||
schedule_frame_callbacks(state, node, crtc, next);
|
schedule_frame_callbacks(state, node, crtc, next);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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<Duration> {
|
||||||
|
// 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);
|
let tolerance = Duration::from_micros(500).min(self.interval / 8);
|
||||||
if target <= now
|
if target <= now
|
||||||
&& self
|
&& self
|
||||||
.last_callback
|
.last_callback
|
||||||
.is_none_or(|last| last + self.interval <= now + tolerance)
|
.is_none_or(|last| last + self.interval <= now + tolerance)
|
||||||
{
|
{
|
||||||
return now;
|
return Some(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
let earliest = self
|
let earliest = self
|
||||||
@@ -207,7 +213,7 @@ impl FrameScheduler {
|
|||||||
.map_or(now, |last| last + self.interval)
|
.map_or(now, |last| last + self.interval)
|
||||||
.max(target)
|
.max(target)
|
||||||
.max(now);
|
.max(now);
|
||||||
self.phase_at_or_after(earliest)
|
Some(self.phase_at_or_after(earliest))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn callback_sent(&mut self, now: Duration) {
|
pub fn callback_sent(&mut self, now: Duration) {
|
||||||
@@ -298,7 +304,35 @@ mod tests {
|
|||||||
scheduler.callback_sent(ms(110));
|
scheduler.callback_sent(ms(110));
|
||||||
scheduler.request(ms(108));
|
scheduler.request(ms(108));
|
||||||
assert_eq!(scheduler.plan(ms(108)).unwrap().present_at, ms(120));
|
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]
|
#[test]
|
||||||
@@ -323,9 +357,9 @@ mod tests {
|
|||||||
scheduler.callback_sent(ms(100) + Duration::from_micros(80));
|
scheduler.callback_sent(ms(100) + Duration::from_micros(80));
|
||||||
scheduler.presented(ms(110));
|
scheduler.presented(ms(110));
|
||||||
let now = ms(110) + Duration::from_micros(10);
|
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);
|
scheduler.callback_sent(now);
|
||||||
assert_eq!(scheduler.callback_deadline(now, now), ms(120));
|
assert_eq!(scheduler.callback_deadline(now, now).unwrap(), ms(120));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -430,19 +430,21 @@ mod tests {
|
|||||||
let start = Duration::ZERO;
|
let start = Duration::ZERO;
|
||||||
let refresh = Duration::from_millis(16);
|
let refresh = Duration::from_millis(16);
|
||||||
let mut clock = crate::frame_scheduler::FrameScheduler::new(refresh);
|
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);
|
clock.callback_sent(start);
|
||||||
for millisecond in 1..16 {
|
for millisecond in 1..16 {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
clock.callback_deadline(start + Duration::from_millis(millisecond), start),
|
clock
|
||||||
|
.callback_deadline(start + Duration::from_millis(millisecond), start)
|
||||||
|
.unwrap(),
|
||||||
start + refresh
|
start + refresh
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let late = start + Duration::from_millis(50);
|
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);
|
clock.callback_sent(late);
|
||||||
assert_eq!(clock.callback_deadline(late, late), late + refresh);
|
assert_eq!(clock.callback_deadline(late, late).unwrap(), late + refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -454,11 +456,11 @@ mod tests {
|
|||||||
fast.callback_sent(start);
|
fast.callback_sent(start);
|
||||||
fast.callback_sent(start + Duration::from_millis(8));
|
fast.callback_sent(start + Duration::from_millis(8));
|
||||||
assert_eq!(
|
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)
|
start + Duration::from_millis(16)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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)
|
start + Duration::from_millis(16)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
|
|||||||
UniformName::new("opacity", UniformType::_1f),
|
UniformName::new("opacity", UniformType::_1f),
|
||||||
UniformName::new("shadow_color", UniformType::_4f),
|
UniformName::new("shadow_color", UniformType::_4f),
|
||||||
];
|
];
|
||||||
let texture = renderer.compile_custom_texture_shader(&corner_shader(ROUNDED_TEXTURE_SHADER), &texture_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 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 shadow = renderer.compile_custom_pixel_shader(corner_shader(WINDOW_SHADOW_SHADER), &shadow_uniforms);
|
||||||
let solid = renderer.compile_custom_pixel_shader(
|
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("clip_rect", UniformType::_4f),
|
||||||
UniformName::new("radius", UniformType::_1f),
|
UniformName::new("radius", UniformType::_1f),
|
||||||
|
|||||||
@@ -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) {
|
if let Some(program) = state.blur_programs.get(&context) {
|
||||||
return Some(program.clone());
|
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) => {
|
Ok(program) => {
|
||||||
let program = BlurProgram(program);
|
let program = BlurProgram(program);
|
||||||
state.blur_programs.insert(context, program.clone());
|
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_rect", UniformType::_4f),
|
||||||
UniformName::new("shadow_values", UniformType::_2f),
|
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) => {
|
Ok(program) => {
|
||||||
let program = MaterialProgram(program);
|
let program = MaterialProgram(program);
|
||||||
state.material_programs.insert(context, program.clone());
|
state.material_programs.insert(context, program.clone());
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ mod tests {
|
|||||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||||
let program = renderer
|
let program = renderer
|
||||||
.compile_custom_pixel_shader(
|
.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("clip_rect", UniformType::_4f),
|
||||||
UniformName::new("radius", UniformType::_1f),
|
UniformName::new("radius", UniformType::_1f),
|
||||||
@@ -599,7 +599,7 @@ mod tests {
|
|||||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||||
for source in [super::ROUNDED_TEXTURE_SHADER, super::BLUR_SHADER] {
|
for source in [super::ROUNDED_TEXTURE_SHADER, super::BLUR_SHADER] {
|
||||||
renderer
|
renderer
|
||||||
.compile_custom_texture_shader(&super::corner_shader(source), &[])
|
.compile_custom_texture_shader(super::corner_shader(source), &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
for source in [
|
for source in [
|
||||||
@@ -609,7 +609,7 @@ mod tests {
|
|||||||
super::MATERIAL_SHADER,
|
super::MATERIAL_SHADER,
|
||||||
] {
|
] {
|
||||||
renderer
|
renderer
|
||||||
.compile_custom_pixel_shader(&super::corner_shader(source), &[])
|
.compile_custom_pixel_shader(super::corner_shader(source), &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,7 +627,7 @@ mod tests {
|
|||||||
let context = EGLContext::new(&display).unwrap();
|
let context = EGLContext::new(&display).unwrap();
|
||||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||||
let program = renderer
|
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();
|
.unwrap();
|
||||||
let size = (32, 32).into();
|
let size = (32, 32).into();
|
||||||
let mut pixels = vec![128u8; 32 * 32 * 4];
|
let mut pixels = vec![128u8; 32 * 32 * 4];
|
||||||
|
|||||||
@@ -562,8 +562,10 @@ mod region_encoding_tests {
|
|||||||
let regions = [[0.25, -0.75, 100.5, 40.25, 14.0]];
|
let regions = [[0.25, -0.75, 100.5, 40.25, 14.0]];
|
||||||
let bytes = super::encode_regions(®ions);
|
let bytes = super::encode_regions(®ions);
|
||||||
let decoded: Vec<_> = bytes
|
let decoded: Vec<_> = bytes
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>()
|
||||||
.map(|v| f32::from_ne_bytes(v.try_into().unwrap()))
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&v| f32::from_ne_bytes(v))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(decoded, regions[0]);
|
assert_eq!(decoded, regions[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user