mirror of https://github.com/yshui/picom.git
core: better frame pacing function
Details explained in the comments on schedule_render(). Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
526853fd3a
commit
fbbfaad97b
|
@ -246,6 +246,9 @@ typedef struct session {
|
||||||
uint64_t last_msc_instant;
|
uint64_t last_msc_instant;
|
||||||
/// The last MSC number
|
/// The last MSC number
|
||||||
uint64_t last_msc;
|
uint64_t last_msc;
|
||||||
|
/// When the currently rendered frame will be displayed.
|
||||||
|
/// 0 means there is no pending frame.
|
||||||
|
uint64_t target_msc;
|
||||||
/// When did we render our last frame.
|
/// When did we render our last frame.
|
||||||
uint64_t last_render;
|
uint64_t last_render;
|
||||||
/// Whether we can perform frame pacing.
|
/// Whether we can perform frame pacing.
|
||||||
|
|
173
src/picom.c
173
src/picom.c
|
@ -208,86 +208,111 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How many seconds into the future should we start rendering the next frame.
|
/// How many seconds into the future should we start rendering the next frame.
|
||||||
double next_frame_offset(session_t *ps) {
|
///
|
||||||
int render_time = rolling_max_get_max(ps->render_stats);
|
/// Renders are scheduled like this:
|
||||||
if (render_time < 0) {
|
///
|
||||||
// We don't have render time estimates, maybe there's no frame rendered
|
/// 1. queue_redraw() registers the intention to render. redraw_needed is set to true to
|
||||||
// yet, or the backend doesn't support render timing information,
|
/// indicate what is on screen needs to be updated.
|
||||||
// queue render immediately.
|
/// 2. then, we need to figure out the best time to start rendering. first, we need to
|
||||||
return 0;
|
/// know when the next frame will be displayed on screen. we have this information from
|
||||||
|
/// the Present extension: we know when was the last frame displayed, and we know the
|
||||||
|
/// refresh rate. so we can calculate the next frame's display time. if our render time
|
||||||
|
/// estimation shows we could miss that target, we push the target back one frame.
|
||||||
|
/// 3. if there is already render completed for that target frame, or there is a render
|
||||||
|
/// currently underway, we don't do anything, and wait for the next Present Complete
|
||||||
|
/// Notify event to try to schedule again.
|
||||||
|
/// 4. otherwise, we schedule a render for that target frame. we use past statistics about
|
||||||
|
/// how long our renders took to figure out when to start rendering. we start rendering
|
||||||
|
/// at the latest point of time possible to still hit the target frame.
|
||||||
|
void schedule_render(session_t *ps) {
|
||||||
|
double delay_s = 0;
|
||||||
|
if (!ps->frame_pacing || !ps->redirected) {
|
||||||
|
// Not doing frame pacing, schedule a render immediately, if not already
|
||||||
|
// scheduled.
|
||||||
|
// If not redirected, we schedule immediately to have a chance to
|
||||||
|
// redirect. We won't have frame or render timing information anyway.
|
||||||
|
if (!ev_is_active(&ps->draw_timer)) {
|
||||||
|
// We don't know the msc, so we set it to 1, because 0 is a
|
||||||
|
// special value
|
||||||
|
ps->target_msc = 1;
|
||||||
|
goto schedule;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct timespec render_time;
|
||||||
|
bool completed =
|
||||||
|
ps->backend_data->ops->last_render_time(ps->backend_data, &render_time);
|
||||||
|
if (!completed || ev_is_active(&ps->draw_timer)) {
|
||||||
|
// There is already a render underway (either just scheduled, or is
|
||||||
|
// rendered but awaiting completion), don't schedule another one.
|
||||||
|
if (ps->target_msc <= ps->last_msc) {
|
||||||
|
log_debug("Target frame %ld is in the past, but we are still "
|
||||||
|
"rendering",
|
||||||
|
ps->target_msc);
|
||||||
|
// We missed our target, push it back one frame
|
||||||
|
ps->target_msc = ps->last_msc + 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ps->target_msc > ps->last_msc) {
|
||||||
|
// Render for the target frame is completed, and has yet to be displayed.
|
||||||
|
// Don't schedule another render.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
int frame_time = (int)rolling_avg_get_avg(ps->frame_time);
|
|
||||||
auto next_msc = ps->last_msc_instant + (uint64_t)frame_time;
|
|
||||||
auto deadline = next_msc - (uint64_t)render_time;
|
|
||||||
|
|
||||||
const uint64_t slack = 1000;
|
|
||||||
struct timespec now;
|
struct timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000;
|
auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000;
|
||||||
if (now_us + slack >= deadline) {
|
|
||||||
// We are already late, render immediately.
|
|
||||||
log_trace("Already late, rendering immediately, last_msc: %" PRIu64
|
|
||||||
", render_time: %d, frame_time: %d, now_us: %" PRIu64,
|
|
||||||
ps->last_msc_instant, render_time, frame_time, now_us);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
log_trace("Delay: %lf s, last_msc: %" PRIu64 ", render_time: %d, frame_time: %d, "
|
|
||||||
"now_us: %" PRIu64 ", next_msc: %" PRIu64,
|
|
||||||
(double)(deadline - now_us - slack) / 1000000.0, ps->last_msc_instant,
|
|
||||||
render_time, frame_time, now_us, next_msc);
|
|
||||||
return (double)(deadline - now_us - slack) / 1000000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update render stats. Return false if a frame is still being rendered.
|
int render_time_us =
|
||||||
static bool update_render_stats(session_t *ps) {
|
(int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L);
|
||||||
if (!ps->first_frame && ps->redirected && ps->backend_data->ops->last_render_time) {
|
if (ps->target_msc == ps->last_msc) {
|
||||||
struct timespec render_time;
|
// The frame has just been displayed, record its render time;
|
||||||
if (ps->backend_data->ops->last_render_time(ps->backend_data, &render_time)) {
|
log_trace("Last render call took: %d us, rolling_max: %d us",
|
||||||
int render_time_us =
|
render_time_us, rolling_max_get_max(ps->render_stats));
|
||||||
(int)(render_time.tv_sec * 1000000 + render_time.tv_nsec / 1000);
|
rolling_max_push(ps->render_stats, render_time_us);
|
||||||
log_trace("Last render call took: %d us, "
|
ps->target_msc = 0;
|
||||||
"rolling_max: %d us",
|
|
||||||
render_time_us, rolling_max_get_max(ps->render_stats));
|
|
||||||
rolling_max_push(ps->render_stats, render_time_us);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
// TODO(yshui): why 1500?
|
||||||
|
const int SLACK = 1500;
|
||||||
|
int render_time_estimate = rolling_max_get_max(ps->render_stats);
|
||||||
|
if (render_time_estimate < 0) {
|
||||||
|
// We don't have render time estimates, maybe there's no frame rendered
|
||||||
|
// yet, or the backend doesn't support render timing information,
|
||||||
|
// schedule render immediately.
|
||||||
|
ps->target_msc = ps->last_msc + 1;
|
||||||
|
goto schedule;
|
||||||
|
}
|
||||||
|
render_time_estimate += SLACK;
|
||||||
|
|
||||||
|
auto frame_time = (uint64_t)rolling_avg_get_avg(ps->frame_time);
|
||||||
|
auto minimal_target_us = now_us + (uint64_t)render_time_estimate;
|
||||||
|
auto frame_delay = (uint64_t)ceil(
|
||||||
|
(double)(minimal_target_us - ps->last_msc_instant) / (double)frame_time);
|
||||||
|
auto deadline = ps->last_msc_instant + frame_delay * frame_time;
|
||||||
|
|
||||||
|
ps->target_msc = ps->last_msc + frame_delay;
|
||||||
|
auto delay = deadline - (uint64_t)render_time_estimate - now_us;
|
||||||
|
delay_s = (double)delay / 1000000.0;
|
||||||
|
|
||||||
|
log_trace("Delay: %lu us, last_msc: %" PRIu64 ", render_time: %d, frame_time: "
|
||||||
|
"%" PRIu64 ", now_us: %" PRIu64 ", next_msc: %" PRIu64,
|
||||||
|
delay, ps->last_msc_instant, render_time_estimate, frame_time, now_us,
|
||||||
|
deadline);
|
||||||
|
|
||||||
|
schedule:
|
||||||
|
assert(!ev_is_active(&ps->draw_timer));
|
||||||
|
ev_timer_set(&ps->draw_timer, delay_s, 0);
|
||||||
|
ev_timer_start(ps->loop, &ps->draw_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void queue_redraw(session_t *ps) {
|
void queue_redraw(session_t *ps) {
|
||||||
// Whether we have already rendered for the current frame.
|
// Whether we have already rendered for the current frame.
|
||||||
// If frame pacing is not enabled, pretend this is false.
|
// If frame pacing is not enabled, pretend this is false.
|
||||||
bool already_rendered = (ps->last_render > ps->last_msc_instant) && ps->frame_pacing;
|
|
||||||
if (already_rendered) {
|
|
||||||
log_debug("Already rendered for the current frame, not queuing "
|
|
||||||
"redraw");
|
|
||||||
}
|
|
||||||
// If --benchmark is used, redraw is always queued
|
// If --benchmark is used, redraw is always queued
|
||||||
if (!ps->redraw_needed && !ps->o.benchmark && !already_rendered) {
|
if (!ps->redraw_needed && !ps->o.benchmark) {
|
||||||
double delay = 0;
|
schedule_render(ps);
|
||||||
if (ps->frame_pacing) {
|
|
||||||
if (!update_render_stats(ps)) {
|
|
||||||
// A frame is still being rendered. This means we missed
|
|
||||||
// previous vblank. We shouldn't queue a new frame until
|
|
||||||
// the next vblank.
|
|
||||||
log_debug("A frame is still being rendered, not queueing "
|
|
||||||
"redraw");
|
|
||||||
ps->redraw_needed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Our loop can be blocked by frame present, which cause ev_now to
|
|
||||||
// drift away from the real time. We need to correct it.
|
|
||||||
delay = next_frame_offset(ps) + ev_now(ps->loop) - ev_time();
|
|
||||||
if (delay < 0) {
|
|
||||||
delay = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Not doing frame pacing, just redraw immediately
|
|
||||||
ev_timer_set(&ps->draw_timer, delay, 0);
|
|
||||||
assert(!ev_is_active(&ps->draw_timer));
|
|
||||||
ev_timer_start(ps->loop, &ps->draw_timer);
|
|
||||||
}
|
}
|
||||||
ps->redraw_needed = true;
|
ps->redraw_needed = true;
|
||||||
}
|
}
|
||||||
|
@ -1557,18 +1582,8 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_
|
||||||
// pacing. Unconditionally queue a frame for simplicity.
|
// pacing. Unconditionally queue a frame for simplicity.
|
||||||
queue_redraw(ps);
|
queue_redraw(ps);
|
||||||
}
|
}
|
||||||
if (ps->frame_pacing && ps->redraw_needed && !ev_is_active(&ps->draw_timer)) {
|
if (ps->frame_pacing && ps->redraw_needed) {
|
||||||
log_trace("Frame pacing: queueing redraw");
|
schedule_render(ps);
|
||||||
// We deferred a frame in queue_redraw() because of frame pacing. Schedule
|
|
||||||
// it now.
|
|
||||||
if (!update_render_stats(ps)) {
|
|
||||||
log_warn("A frame is still being rendered, not queueing redraw.");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
ev_timer_set(&ps->draw_timer,
|
|
||||||
next_frame_offset(ps) + ev_now(ps->loop) - ev_time(), 0);
|
|
||||||
ev_timer_start(ps->loop, &ps->draw_timer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue