blob: d5fdd42a76bead094aceef493926d198a9354f12 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{
PresentParameters, PresentationInfo, SchedulingFuture, SchedulingFutureState, SchedulingLib,
};
use async_trait::async_trait;
use fuchsia_async::Time as fasync_time;
use std::cell::{Cell, RefCell};
use std::task::Waker;
use {fuchsia_trace as trace, fuchsia_zircon as zx};
// Scheduler for maximum throughput. Tries to schedule a frame at each on_next_frame_begin, if
// there's something to draw (i.e. request_present() has been called). Presents are always
// squashable, so if scenic misses a deadline the frame may be dropped.
//
// Notes:
// Does not track present credits. Since it's limited to at most one Present() call per
// OnNextFrameBegin() event, we're guaranteed not to run out of credits.
//
// TODO(https://fxbug.dev/42163690): Due OnNextFrameBegin() currently only firing after acquire fences complete
// this scheduler will not manage to produce pipelined frames. This should resolve itself once
// the bug is resolved, but testing to confirm will be necessary.
pub struct ThroughputScheduler {
data: RefCell<WakeupData>,
next_expected_times: Cell<PresentationInfo>,
wait_guard: RefCell<()>,
}
// Data used to determine when to wake up. Checked as part of SchedulingFuture polling.
// Must be separate to satisfy Pin<> in SchedulingFuture.
struct WakeupData {
frame_requested: bool,
next_frame_begin: bool,
waker: Option<Waker>,
}
impl ThroughputScheduler {
pub fn new() -> ThroughputScheduler {
let now = fasync_time::now().into_zx();
ThroughputScheduler {
data: RefCell::new(WakeupData {
frame_requested: false,
next_frame_begin: true,
waker: None,
}),
next_expected_times: Cell::new(PresentationInfo {
latch_point: now,
presentation_time: now,
}),
wait_guard: RefCell::new(()),
}
}
}
#[async_trait(?Send)]
impl SchedulingLib for ThroughputScheduler {
fn request_present(&self) {
self.data.borrow_mut().frame_requested = true;
self.data.borrow().maybe_wakeup();
}
fn on_next_frame_begin(
&self,
_additional_present_credits: u32,
future_presentation_infos: Vec<PresentationInfo>,
) {
assert!(!future_presentation_infos.is_empty());
self.next_expected_times.set(future_presentation_infos[0]);
self.data.borrow_mut().next_frame_begin = true;
self.data.borrow().maybe_wakeup();
}
// Waits until the next on_next_frame_begin() after a request_frame().
async fn wait_to_update(&self) -> PresentParameters {
// Mutably borrow the wait_guard to prevent any simultaneous waits.
// TODO: this variable triggered the `must_not_suspend` lint and may be held across an await
// If this is the case, it is an error. See https://fxbug.dev/42168913 for more details
let _guard = self.wait_guard.try_borrow_mut().expect("Only one wait at a time allowed");
// Async tracing for the waiting period
let _trace_guard =
trace::async_enter!(trace::Id::new(), c"gfx", c"ThroughputScheduler::WaitForPresent");
// Wait until we're ready to draw.
SchedulingFuture { sched: &self.data }.await;
{
// Update state for next frame.
let mut data = self.data.borrow_mut();
data.frame_requested = false;
data.next_frame_begin = false;
data.waker = None;
}
let PresentationInfo { latch_point, presentation_time } = self.next_expected_times.get();
PresentParameters {
expected_latch_point: latch_point,
expected_presentation_time: presentation_time,
requested_presentation_time: zx::Time::from_nanos(0),
unsquashable: false,
}
}
}
impl SchedulingFutureState for WakeupData {
fn ready_to_wake_up(&self) -> bool {
self.frame_requested && self.next_frame_begin
}
fn set_waker(&mut self, waker: Waker) {
self.waker = Some(waker);
}
fn get_waker(&self) -> &Option<Waker> {
&self.waker
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fuchsia_async as fasync;
use std::task::Poll;
#[test]
fn wait_without_request_present_never_completes() {
let mut exec = fasync::TestExecutor::new();
let sched = ThroughputScheduler::new();
let mut fut = sched.wait_to_update();
assert!(exec.run_until_stalled(&mut fut).is_pending());
// Should complete after request_present().
sched.request_present();
assert_matches!(
exec.run_until_stalled(&mut fut),
Poll::Ready(PresentParameters {
expected_latch_point: _,
expected_presentation_time: _,
requested_presentation_time: _,
unsquashable: false,
})
);
}
#[fasync::run_until_stalled(test)]
async fn wait_after_initial_request_present_completes_immediately() {
let sched = ThroughputScheduler::new();
sched.request_present();
assert_matches!(
sched.wait_to_update().await,
PresentParameters {
expected_latch_point: _,
expected_presentation_time: _,
requested_presentation_time: _,
unsquashable: false,
}
);
}
#[test]
fn following_waits_never_completes_without_on_next_frame_begin() {
let mut exec = fasync::TestExecutor::new();
let sched = ThroughputScheduler::new();
// Initial wait always completes immediately.
sched.request_present();
let mut fut = sched.wait_to_update();
exec.run_until_stalled(&mut fut).is_ready();
// Second wait doesn't complete until after on_frame_presented().
sched.request_present();
let mut fut = sched.wait_to_update();
assert!(exec.run_until_stalled(&mut fut).is_pending());
sched.on_next_frame_begin(
10,
vec![PresentationInfo {
latch_point: zx::Time::from_nanos(1),
presentation_time: zx::Time::from_nanos(1),
}],
);
assert_eq!(
exec.run_until_stalled(&mut fut),
Poll::Ready(PresentParameters {
expected_latch_point: zx::Time::from_nanos(1),
expected_presentation_time: zx::Time::from_nanos(1),
requested_presentation_time: zx::Time::from_nanos(0),
unsquashable: false,
})
);
}
}