mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-04-13 08:22:16 -04:00
wlr_frame_scheduler: Add predictive frame scheduler
The predictive frame scheduler builds upon the presentation scheduler, adding a delay based on the measured CPU and GPU time required to prepare a frame to place the frame event as close as safely possible to the commit deadline.
This commit is contained in:
parent
e70de3ffaa
commit
df562a8e70
5 changed files with 246 additions and 48 deletions
|
|
@ -10,9 +10,11 @@
|
|||
#define WLR_TYPES_WLR_FRAME_SCHEDULER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <wayland-server-core.h>
|
||||
|
||||
struct wlr_frame_scheduler_impl;
|
||||
struct wlr_render_timer;
|
||||
|
||||
struct wlr_frame_scheduler {
|
||||
struct wlr_output *output;
|
||||
|
|
@ -65,4 +67,23 @@ void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler);
|
|||
void wlr_frame_scheduler_emit_frame(struct wlr_frame_scheduler *scheduler);
|
||||
void wlr_frame_scheduler_destroy(struct wlr_frame_scheduler *scheduler);
|
||||
|
||||
/**
|
||||
* The predictive scheduler maintains a render loop based on `wlr_output.events.present`, and
|
||||
* schedules frame signals to arrive just before the estimated render deadline. It learns from
|
||||
* historic render times provided via wlr_frame_scheduler_inform_render().
|
||||
*/
|
||||
struct wlr_frame_scheduler *wlr_predictive_frame_scheduler_create(struct wlr_output *output);
|
||||
/**
|
||||
* Provide render timing feedback to the scheduler. Must be called after each output commit.
|
||||
*
|
||||
* pre_render_duration_ns is the wall time from the frame signal to the start of the render pass.
|
||||
*
|
||||
* render_timer is the GPU render timer for this frame. May be NULL if no rendering was performed
|
||||
* (e.g. direct scanout), in which case GPU render time is assumed to be zero.
|
||||
*
|
||||
* This is a no-op for non-predictive schedulers.
|
||||
*/
|
||||
void wlr_frame_scheduler_inform_render(struct wlr_frame_scheduler *scheduler,
|
||||
int64_t pre_render_duration_ns, struct wlr_render_timer *render_timer);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -268,12 +268,9 @@ struct wlr_scene_output {
|
|||
uint64_t in_point;
|
||||
struct wlr_drm_syncobj_timeline *out_timeline;
|
||||
uint64_t out_point;
|
||||
} WLR_PRIVATE;
|
||||
};
|
||||
|
||||
struct wlr_scene_timer {
|
||||
int64_t pre_render_duration;
|
||||
struct wlr_render_timer *render_timer;
|
||||
struct wlr_render_timer *render_timer;
|
||||
} WLR_PRIVATE;
|
||||
};
|
||||
|
||||
/** A layer shell scene helper */
|
||||
|
|
@ -601,8 +598,6 @@ void wlr_scene_output_set_frame_scheduler(struct wlr_scene_output *scene_output,
|
|||
struct wlr_frame_scheduler *scheduler);
|
||||
|
||||
struct wlr_scene_output_state_options {
|
||||
struct wlr_scene_timer *timer;
|
||||
|
||||
/**
|
||||
* Color transform to apply before the output's color transform. Cannot be
|
||||
* used when the output has a non-NULL image description set.
|
||||
|
|
@ -629,15 +624,6 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
|
|||
bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
||||
struct wlr_output_state *state, const struct wlr_scene_output_state_options *options);
|
||||
|
||||
/**
|
||||
* Retrieve the duration in nanoseconds between the last wlr_scene_output_commit() call and the end
|
||||
* of its operations, including those on the GPU that may have finished after the call returned.
|
||||
*
|
||||
* Returns -1 if the duration is unavailable.
|
||||
*/
|
||||
int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer);
|
||||
void wlr_scene_timer_finish(struct wlr_scene_timer *timer);
|
||||
|
||||
/**
|
||||
* Call wlr_surface_send_frame_done() on all surfaces in the scene rendered by
|
||||
* wlr_scene_output_commit() for which wlr_scene_surface.primary_output
|
||||
|
|
|
|||
|
|
@ -329,6 +329,9 @@ int wlr_render_timer_get_duration_ns(struct wlr_render_timer *timer) {
|
|||
}
|
||||
|
||||
void wlr_render_timer_destroy(struct wlr_render_timer *timer) {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
if (!timer->impl->destroy) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1781,6 +1781,8 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
|
|||
|
||||
scene_output_update_geometry(scene_output, false);
|
||||
|
||||
scene_output->render_timer = wlr_render_timer_create(output->renderer);
|
||||
|
||||
return scene_output;
|
||||
}
|
||||
|
||||
|
|
@ -1818,6 +1820,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
|
|||
}
|
||||
|
||||
wlr_addon_finish(&scene_output->addon);
|
||||
wlr_render_timer_destroy(scene_output->render_timer);
|
||||
wlr_frame_scheduler_destroy(scene_output->frame_scheduler);
|
||||
wlr_damage_ring_finish(&scene_output->damage_ring);
|
||||
pixman_region32_fini(&scene_output->pending_commit_damage);
|
||||
|
|
@ -2279,13 +2282,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|||
if (!options) {
|
||||
options = &default_options;
|
||||
}
|
||||
struct wlr_scene_timer *timer = options->timer;
|
||||
struct timespec start_time;
|
||||
if (timer) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
wlr_scene_timer_finish(timer);
|
||||
*timer = (struct wlr_scene_timer){0};
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
|
||||
if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) {
|
||||
// if the state is being disabled, do nothing.
|
||||
|
|
@ -2432,12 +2430,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|||
if (scanout) {
|
||||
scene_output_state_attempt_gamma(scene_output, state);
|
||||
|
||||
if (timer) {
|
||||
struct timespec end_time, duration;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
timespec_sub(&duration, &end_time, &start_time);
|
||||
timer->pre_render_duration = timespec_to_nsec(&duration);
|
||||
}
|
||||
struct timespec end_time, duration;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
timespec_sub(&duration, &end_time, &start_time);
|
||||
wlr_frame_scheduler_inform_render(scene_output->frame_scheduler,
|
||||
timespec_to_nsec(&duration), NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2457,13 +2454,12 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|||
|
||||
assert(buffer->width == resolution_width && buffer->height == resolution_height);
|
||||
|
||||
if (timer) {
|
||||
timer->render_timer = wlr_render_timer_create(output->renderer);
|
||||
|
||||
int64_t pre_render_duration_ns;
|
||||
{
|
||||
struct timespec end_time, duration;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
timespec_sub(&duration, &end_time, &start_time);
|
||||
timer->pre_render_duration = timespec_to_nsec(&duration);
|
||||
pre_render_duration_ns = timespec_to_nsec(&duration);
|
||||
}
|
||||
|
||||
if ((render_gamma_lut
|
||||
|
|
@ -2482,7 +2478,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|||
scene_output->in_point++;
|
||||
struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer,
|
||||
&(struct wlr_buffer_pass_options){
|
||||
.timer = timer ? timer->render_timer : NULL,
|
||||
.timer = scene_output->render_timer,
|
||||
.color_transform = scene_output->combined_color_transform,
|
||||
.signal_timeline = scene_output->in_timeline,
|
||||
.signal_point = scene_output->in_point,
|
||||
|
|
@ -2606,24 +2602,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|||
scene_output_state_attempt_gamma(scene_output, state);
|
||||
}
|
||||
|
||||
wlr_frame_scheduler_inform_render(scene_output->frame_scheduler,
|
||||
pre_render_duration_ns, scene_output->render_timer);
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) {
|
||||
int64_t pre_render = timer->pre_render_duration;
|
||||
if (!timer->render_timer) {
|
||||
return pre_render;
|
||||
}
|
||||
int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer);
|
||||
return render != -1 ? pre_render + render : -1;
|
||||
}
|
||||
|
||||
void wlr_scene_timer_finish(struct wlr_scene_timer *timer) {
|
||||
if (timer->render_timer) {
|
||||
wlr_render_timer_destroy(timer->render_timer);
|
||||
}
|
||||
}
|
||||
|
||||
static void scene_node_send_frame_done(struct wlr_scene_node *node,
|
||||
struct wlr_scene_output *scene_output, struct timespec *now) {
|
||||
if (!node->enabled) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <assert.h>
|
||||
#include <backend/headless.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <types/wlr_output.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-server-core.h>
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
#include <wlr/backend/wayland.h>
|
||||
#include <wlr/interfaces/wlr_frame_scheduler.h>
|
||||
#include <wlr/interfaces/wlr_output.h>
|
||||
#include <wlr/render/wlr_renderer.h>
|
||||
#include <wlr/types/wlr_frame_scheduler.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/util/log.h>
|
||||
|
|
@ -283,6 +285,209 @@ struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *out
|
|||
return &scheduler->base.base;
|
||||
}
|
||||
|
||||
// The predictive scheduler schedules frame signals based on historic render times, aiming to
|
||||
// start rendering as late as possible while still meeting the next vblank deadline.
|
||||
struct predictive_scheduler {
|
||||
struct wlr_frame_scheduler base;
|
||||
struct wl_event_source *idle;
|
||||
struct wl_event_source *timer;
|
||||
struct wl_listener commit;
|
||||
struct wl_listener present;
|
||||
|
||||
bool frame_pending;
|
||||
|
||||
int64_t estimated_frame_time_ns;
|
||||
bool has_estimate;
|
||||
|
||||
int64_t pending_pre_render_ns;
|
||||
struct wlr_render_timer *pending_render_timer;
|
||||
bool has_pending_feedback;
|
||||
};
|
||||
|
||||
static const struct wlr_frame_scheduler_impl predictive_scheduler_impl;
|
||||
|
||||
static int64_t compute_margin_ns(int refresh_ns) {
|
||||
int64_t margin = refresh_ns / 20;
|
||||
if (margin < 1500000) {
|
||||
margin = 1500000;
|
||||
}
|
||||
return margin;
|
||||
}
|
||||
|
||||
static void update_estimate(struct predictive_scheduler *scheduler, int64_t sample_ns) {
|
||||
if (!scheduler->has_estimate) {
|
||||
scheduler->estimated_frame_time_ns = sample_ns;
|
||||
scheduler->has_estimate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sample_ns > scheduler->estimated_frame_time_ns) {
|
||||
// React quickly to spikes
|
||||
scheduler->estimated_frame_time_ns =
|
||||
(9 * sample_ns + scheduler->estimated_frame_time_ns) / 10;
|
||||
} else {
|
||||
// Decay slowly
|
||||
scheduler->estimated_frame_time_ns =
|
||||
(sample_ns + 9 * scheduler->estimated_frame_time_ns) / 10;
|
||||
}
|
||||
}
|
||||
|
||||
static void predictive_scheduler_consume_feedback(struct predictive_scheduler *scheduler) {
|
||||
if (!scheduler->has_pending_feedback) {
|
||||
return;
|
||||
}
|
||||
scheduler->has_pending_feedback = false;
|
||||
|
||||
int64_t gpu_ns = 0;
|
||||
if (scheduler->pending_render_timer != NULL) {
|
||||
int duration = wlr_render_timer_get_duration_ns(scheduler->pending_render_timer);
|
||||
if (duration > 0) {
|
||||
gpu_ns = duration;
|
||||
}
|
||||
}
|
||||
|
||||
update_estimate(scheduler, scheduler->pending_pre_render_ns + gpu_ns);
|
||||
}
|
||||
|
||||
static void predictive_scheduler_handle_idle(void *data) {
|
||||
struct predictive_scheduler *scheduler = data;
|
||||
scheduler->idle = NULL;
|
||||
scheduler->frame_pending = false;
|
||||
wlr_frame_scheduler_emit_frame(&scheduler->base);
|
||||
}
|
||||
|
||||
static int predictive_scheduler_handle_timer(void *data) {
|
||||
struct predictive_scheduler *scheduler = data;
|
||||
scheduler->frame_pending = false;
|
||||
wlr_frame_scheduler_emit_frame(&scheduler->base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void predictive_scheduler_set_timer(struct predictive_scheduler *scheduler, int ms) {
|
||||
if (scheduler->idle != NULL) {
|
||||
wl_event_source_remove(scheduler->idle);
|
||||
scheduler->idle = NULL;
|
||||
}
|
||||
wl_event_source_timer_update(scheduler->timer, ms);
|
||||
}
|
||||
|
||||
static void predictive_scheduler_set_idle(struct predictive_scheduler *scheduler) {
|
||||
wl_event_source_timer_update(scheduler->timer, 0);
|
||||
if (!scheduler->idle) {
|
||||
scheduler->idle = wl_event_loop_add_idle(
|
||||
scheduler->base.output->event_loop,
|
||||
predictive_scheduler_handle_idle, scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
static void predictive_scheduler_cancel(struct predictive_scheduler *scheduler) {
|
||||
if (scheduler->idle != NULL) {
|
||||
wl_event_source_remove(scheduler->idle);
|
||||
scheduler->idle = NULL;
|
||||
}
|
||||
wl_event_source_timer_update(scheduler->timer, 0);
|
||||
}
|
||||
|
||||
static void predictive_scheduler_handle_commit(struct wl_listener *listener, void *data) {
|
||||
struct predictive_scheduler *scheduler = wl_container_of(listener, scheduler, commit);
|
||||
if (scheduler->base.output->enabled) {
|
||||
scheduler->frame_pending = true;
|
||||
predictive_scheduler_cancel(scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
static void predictive_scheduler_handle_present(struct wl_listener *listener, void *data) {
|
||||
struct predictive_scheduler *scheduler = wl_container_of(listener, scheduler, present);
|
||||
struct wlr_output_event_present *event = data;
|
||||
|
||||
if (!event->presented) {
|
||||
scheduler->frame_pending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
predictive_scheduler_consume_feedback(scheduler);
|
||||
|
||||
if (!scheduler->has_estimate || event->refresh == 0) {
|
||||
predictive_scheduler_set_idle(scheduler);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t refresh_ns = event->refresh;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
int64_t present_ns = (int64_t)event->when.tv_sec * 1000000000 + event->when.tv_nsec;
|
||||
int64_t now_ns = (int64_t)now.tv_sec * 1000000000 + now.tv_nsec;
|
||||
int64_t next_vblank_ns = present_ns + refresh_ns;
|
||||
|
||||
int64_t margin = compute_margin_ns(refresh_ns);
|
||||
int64_t deadline_ns = next_vblank_ns - scheduler->estimated_frame_time_ns - margin;
|
||||
int64_t delay_ns = deadline_ns - now_ns;
|
||||
|
||||
if (delay_ns < 1000000) {
|
||||
predictive_scheduler_set_idle(scheduler);
|
||||
} else {
|
||||
int delay_ms = (int)(delay_ns / 1000000);
|
||||
predictive_scheduler_set_timer(scheduler, delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static void predictive_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct predictive_scheduler *scheduler =
|
||||
wl_container_of(wlr_scheduler, scheduler, base);
|
||||
if (scheduler->idle != NULL || scheduler->frame_pending) {
|
||||
return;
|
||||
}
|
||||
predictive_scheduler_set_idle(scheduler);
|
||||
}
|
||||
|
||||
static void predictive_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct predictive_scheduler *scheduler =
|
||||
wl_container_of(wlr_scheduler, scheduler, base);
|
||||
if (scheduler->idle != NULL) {
|
||||
wl_event_source_remove(scheduler->idle);
|
||||
}
|
||||
wl_event_source_remove(scheduler->timer);
|
||||
wl_list_remove(&scheduler->commit.link);
|
||||
wl_list_remove(&scheduler->present.link);
|
||||
free(scheduler);
|
||||
}
|
||||
|
||||
static const struct wlr_frame_scheduler_impl predictive_scheduler_impl = {
|
||||
.schedule_frame = predictive_scheduler_schedule_frame,
|
||||
.destroy = predictive_scheduler_destroy,
|
||||
};
|
||||
|
||||
struct wlr_frame_scheduler *wlr_predictive_frame_scheduler_create(struct wlr_output *output) {
|
||||
struct predictive_scheduler *scheduler = calloc(1, sizeof(*scheduler));
|
||||
if (!scheduler) {
|
||||
return NULL;
|
||||
}
|
||||
wlr_frame_scheduler_init(&scheduler->base, &predictive_scheduler_impl, output);
|
||||
|
||||
scheduler->timer = wl_event_loop_add_timer(output->event_loop,
|
||||
predictive_scheduler_handle_timer, scheduler);
|
||||
|
||||
scheduler->commit.notify = predictive_scheduler_handle_commit;
|
||||
wl_signal_add(&output->events.commit, &scheduler->commit);
|
||||
scheduler->present.notify = predictive_scheduler_handle_present;
|
||||
wl_signal_add(&output->events.present, &scheduler->present);
|
||||
return &scheduler->base;
|
||||
}
|
||||
|
||||
void wlr_frame_scheduler_inform_render(struct wlr_frame_scheduler *scheduler,
|
||||
int64_t pre_render_duration_ns, struct wlr_render_timer *render_timer) {
|
||||
if (scheduler->impl != &predictive_scheduler_impl) {
|
||||
return;
|
||||
}
|
||||
struct predictive_scheduler *predictive =
|
||||
wl_container_of(scheduler, predictive, base);
|
||||
|
||||
predictive->pending_pre_render_ns = pre_render_duration_ns;
|
||||
predictive->pending_render_timer = render_timer;
|
||||
predictive->has_pending_feedback = true;
|
||||
}
|
||||
|
||||
struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output) {
|
||||
if (wlr_output_is_wl(output) && !wlr_wl_backend_has_presentation_time(output->backend)) {
|
||||
wlr_log(WLR_INFO, "wp_presentation not available, falling back to frame callbacks");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue