mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-04-13 08:22:16 -04:00
Merge branch 'predictive-frame-scheduling' into 'master'
Draft: wlr_frame_scheduler: Add predictive frame scheduler See merge request wlroots/wlroots!5332
This commit is contained in:
commit
651f85cb81
34 changed files with 882 additions and 220 deletions
|
|
@ -5,6 +5,7 @@
|
|||
#include <wlr/interfaces/wlr_ext_image_capture_source_v1.h>
|
||||
#include <wlr/interfaces/wlr_output.h>
|
||||
#include <wlr/types/wlr_ext_image_copy_capture_v1.h>
|
||||
#include <wlr/types/wlr_frame_scheduler.h>
|
||||
#include <wlr/util/log.h>
|
||||
|
||||
#include "types/wlr_output.h"
|
||||
|
|
@ -134,11 +135,8 @@ static void source_stop(struct wlr_ext_image_capture_source_v1 *base) {
|
|||
static void source_request_frame(struct wlr_ext_image_capture_source_v1 *base,
|
||||
bool schedule_frame) {
|
||||
struct scene_node_source *source = wl_container_of(base, source, base);
|
||||
if (source->output.frame_pending) {
|
||||
wlr_output_send_frame(&source->output);
|
||||
}
|
||||
if (schedule_frame) {
|
||||
wlr_output_update_needs_frame(&source->output);
|
||||
if (schedule_frame && source->scene_output != NULL) {
|
||||
wlr_frame_scheduler_schedule_frame(source->scene_output->frame_scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +244,12 @@ static bool output_commit(struct wlr_output *output, const struct wlr_output_sta
|
|||
|
||||
pixman_region32_fini(&full_damage);
|
||||
|
||||
struct wlr_output_event_present present_event = {
|
||||
.commit_seq = output->commit_seq + 1,
|
||||
.presented = true,
|
||||
};
|
||||
output_defer_present(output, present_event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -283,11 +287,7 @@ static void source_handle_output_frame(struct wl_listener *listener, void *data)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!wlr_scene_output_needs_frame(source->scene_output)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can only emit frames with damage
|
||||
// Only render when there's actual damage to commit
|
||||
if (!pixman_region32_empty(&source->scene_output->pending_commit_damage)) {
|
||||
source_render(source);
|
||||
}
|
||||
|
|
@ -331,7 +331,7 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_w
|
|||
wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy);
|
||||
|
||||
source->output_frame.notify = source_handle_output_frame;
|
||||
wl_signal_add(&source->output.events.frame, &source->output_frame);
|
||||
wl_signal_add(&source->scene_output->frame_scheduler->events.frame, &source->output_frame);
|
||||
|
||||
return &source->base;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ wlr_files += files(
|
|||
'wlr_export_dmabuf_v1.c',
|
||||
'wlr_ext_background_effect_v1.c',
|
||||
'wlr_ext_data_control_v1.c',
|
||||
'wlr_frame_scheduler.c',
|
||||
'wlr_ext_foreign_toplevel_list_v1.c',
|
||||
'wlr_ext_image_copy_capture_v1.c',
|
||||
'wlr_ext_workspace_v1.c',
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend,
|
|||
wl_list_init(&output->layers);
|
||||
wl_list_init(&output->resources);
|
||||
|
||||
wl_signal_init(&output->events.frame);
|
||||
|
||||
wl_signal_init(&output->events.damage);
|
||||
wl_signal_init(&output->events.needs_frame);
|
||||
wl_signal_init(&output->events.precommit);
|
||||
|
|
@ -397,7 +397,6 @@ void wlr_output_finish(struct wlr_output *output) {
|
|||
wl_signal_emit_mutable(&output->events.destroy, output);
|
||||
wlr_addon_set_finish(&output->addons);
|
||||
|
||||
assert(wl_list_empty(&output->events.frame.listener_list));
|
||||
assert(wl_list_empty(&output->events.damage.listener_list));
|
||||
assert(wl_list_empty(&output->events.needs_frame.listener_list));
|
||||
assert(wl_list_empty(&output->events.precommit.listener_list));
|
||||
|
|
@ -430,10 +429,6 @@ void wlr_output_finish(struct wlr_output *output) {
|
|||
|
||||
wlr_swapchain_destroy(output->swapchain);
|
||||
|
||||
if (output->idle_frame != NULL) {
|
||||
wl_event_source_remove(output->idle_frame);
|
||||
}
|
||||
|
||||
if (output->idle_done != NULL) {
|
||||
wl_event_source_remove(output->idle_done);
|
||||
}
|
||||
|
|
@ -763,12 +758,6 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st
|
|||
return false;
|
||||
}
|
||||
|
||||
if ((state->committed & WLR_OUTPUT_STATE_BUFFER) &&
|
||||
output->idle_frame != NULL) {
|
||||
wl_event_source_remove(output->idle_frame);
|
||||
output->idle_frame = NULL;
|
||||
}
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
|
|
@ -785,11 +774,6 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st
|
|||
void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state) {
|
||||
output->commit_seq++;
|
||||
|
||||
if (output_pending_enabled(output, state)) {
|
||||
output->frame_pending = true;
|
||||
output->needs_frame = false;
|
||||
}
|
||||
|
||||
output_apply_state(output, state);
|
||||
}
|
||||
|
||||
|
|
@ -844,37 +828,6 @@ bool wlr_output_commit_state(struct wlr_output *output,
|
|||
return true;
|
||||
}
|
||||
|
||||
void wlr_output_send_frame(struct wlr_output *output) {
|
||||
output->frame_pending = false;
|
||||
if (output->enabled) {
|
||||
wl_signal_emit_mutable(&output->events.frame, output);
|
||||
}
|
||||
}
|
||||
|
||||
static void schedule_frame_handle_idle_timer(void *data) {
|
||||
struct wlr_output *output = data;
|
||||
output->idle_frame = NULL;
|
||||
if (!output->frame_pending) {
|
||||
wlr_output_send_frame(output);
|
||||
}
|
||||
}
|
||||
|
||||
void wlr_output_schedule_frame(struct wlr_output *output) {
|
||||
// Make sure the compositor commits a new frame. This is necessary to make
|
||||
// clients which ask for frame callbacks without submitting a new buffer
|
||||
// work.
|
||||
wlr_output_update_needs_frame(output);
|
||||
|
||||
if (output->frame_pending || output->idle_frame != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're using an idle timer here in case a buffer swap happens right after
|
||||
// this function is called
|
||||
output->idle_frame = wl_event_loop_add_idle(output->event_loop,
|
||||
schedule_frame_handle_idle_timer, output);
|
||||
}
|
||||
|
||||
void wlr_output_send_present(struct wlr_output *output,
|
||||
struct wlr_output_event_present *event) {
|
||||
assert(event);
|
||||
|
|
@ -988,10 +941,6 @@ size_t wlr_output_get_gamma_size(struct wlr_output *output) {
|
|||
}
|
||||
|
||||
void wlr_output_update_needs_frame(struct wlr_output *output) {
|
||||
if (output->needs_frame) {
|
||||
return;
|
||||
}
|
||||
output->needs_frame = true;
|
||||
wl_signal_emit_mutable(&output->events.needs_frame, output);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <wlr/types/wlr_compositor.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include <wlr/types/wlr_fractional_scale_v1.h>
|
||||
#include <wlr/types/wlr_frame_scheduler.h>
|
||||
#include <wlr/types/wlr_linux_drm_syncobj_v1.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/types/wlr_presentation_time.h>
|
||||
|
|
@ -364,7 +365,7 @@ static void handle_scene_surface_surface_commit(
|
|||
|
||||
if (!wl_list_empty(&surface->surface->current.frame_callback_list) &&
|
||||
surface->buffer->primary_output != NULL && enabled) {
|
||||
wlr_output_schedule_frame(surface->buffer->primary_output->output);
|
||||
wlr_frame_scheduler_schedule_frame(surface->buffer->primary_output->frame_scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <wlr/types/wlr_color_management_v1.h>
|
||||
#include <wlr/types/wlr_compositor.h>
|
||||
#include <wlr/types/wlr_damage_ring.h>
|
||||
#include <wlr/types/wlr_frame_scheduler.h>
|
||||
#include <wlr/types/wlr_gamma_control_v1.h>
|
||||
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
||||
#include <wlr/types/wlr_presentation_time.h>
|
||||
|
|
@ -362,7 +363,7 @@ static void scene_output_damage(struct wlr_scene_output *scene_output,
|
|||
pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height);
|
||||
|
||||
if (!pixman_region32_empty(&clipped)) {
|
||||
wlr_output_schedule_frame(scene_output->output);
|
||||
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
|
||||
wlr_damage_ring_add(&scene_output->damage_ring, &clipped);
|
||||
|
||||
pixman_region32_union(&scene_output->pending_commit_damage,
|
||||
|
|
@ -1576,7 +1577,7 @@ static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener *
|
|||
output->gamma_lut = event->control;
|
||||
wlr_color_transform_unref(output->gamma_lut_color_transform);
|
||||
output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control);
|
||||
wlr_output_schedule_frame(output->output);
|
||||
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
|
||||
}
|
||||
|
||||
static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *listener,
|
||||
|
|
@ -1691,7 +1692,7 @@ static void scene_output_handle_commit(struct wl_listener *listener, void *data)
|
|||
|
||||
if (scene_output->scene->debug_damage_option == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT &&
|
||||
!wl_list_empty(&scene_output->damage_highlight_regions)) {
|
||||
wlr_output_schedule_frame(scene_output->output);
|
||||
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
|
||||
}
|
||||
|
||||
// Next time the output is enabled, try to re-apply the gamma LUT
|
||||
|
|
@ -1720,12 +1721,6 @@ static void scene_output_handle_damage(struct wl_listener *listener, void *data)
|
|||
pixman_region32_fini(&damage);
|
||||
}
|
||||
|
||||
static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) {
|
||||
struct wlr_scene_output *scene_output = wl_container_of(listener,
|
||||
scene_output, output_needs_frame);
|
||||
wlr_output_schedule_frame(scene_output->output);
|
||||
}
|
||||
|
||||
struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
|
||||
struct wlr_output *output) {
|
||||
struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_output));
|
||||
|
|
@ -1733,6 +1728,12 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
scene_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
|
||||
if (scene_output->frame_scheduler == NULL) {
|
||||
free(scene_output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
scene_output->output = output;
|
||||
scene_output->scene = scene;
|
||||
wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl);
|
||||
|
|
@ -1778,11 +1779,10 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
|
|||
scene_output->output_damage.notify = scene_output_handle_damage;
|
||||
wl_signal_add(&output->events.damage, &scene_output->output_damage);
|
||||
|
||||
scene_output->output_needs_frame.notify = scene_output_handle_needs_frame;
|
||||
wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame);
|
||||
|
||||
scene_output_update_geometry(scene_output, false);
|
||||
|
||||
scene_output->render_timer = wlr_render_timer_create(output->renderer);
|
||||
|
||||
return scene_output;
|
||||
}
|
||||
|
||||
|
|
@ -1792,6 +1792,16 @@ static void highlight_region_destroy(struct highlight_region *damage) {
|
|||
free(damage);
|
||||
}
|
||||
|
||||
void wlr_scene_output_set_frame_scheduler(struct wlr_scene_output *scene_output,
|
||||
struct wlr_frame_scheduler *scheduler) {
|
||||
bool needs_frame = scene_output->frame_scheduler->needs_frame;
|
||||
wlr_frame_scheduler_destroy(scene_output->frame_scheduler);
|
||||
scene_output->frame_scheduler = scheduler;
|
||||
if (needs_frame) {
|
||||
wlr_frame_scheduler_schedule_frame(scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
|
||||
if (scene_output == NULL) {
|
||||
return;
|
||||
|
|
@ -1810,12 +1820,13 @@ 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);
|
||||
wl_list_remove(&scene_output->link);
|
||||
wl_list_remove(&scene_output->output_commit.link);
|
||||
wl_list_remove(&scene_output->output_damage.link);
|
||||
wl_list_remove(&scene_output->output_needs_frame.link);
|
||||
if (scene_output->in_timeline != NULL) {
|
||||
wlr_drm_syncobj_timeline_signal(scene_output->in_timeline, UINT64_MAX);
|
||||
wlr_drm_syncobj_timeline_unref(scene_output->in_timeline);
|
||||
|
|
@ -2145,18 +2156,8 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout(
|
|||
return SCANOUT_SUCCESS;
|
||||
}
|
||||
|
||||
bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output) {
|
||||
return scene_output->output->needs_frame ||
|
||||
!pixman_region32_empty(&scene_output->pending_commit_damage) ||
|
||||
scene_output->gamma_lut_changed;
|
||||
}
|
||||
|
||||
bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
|
||||
const struct wlr_scene_output_state_options *options) {
|
||||
if (!wlr_scene_output_needs_frame(scene_output)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
struct wlr_output_state state;
|
||||
wlr_output_state_init(&state);
|
||||
|
|
@ -2281,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.
|
||||
|
|
@ -2434,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;
|
||||
}
|
||||
|
||||
|
|
@ -2459,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
|
||||
|
|
@ -2484,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,
|
||||
|
|
@ -2608,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) {
|
||||
|
|
|
|||
502
types/wlr_frame_scheduler.c
Normal file
502
types/wlr_frame_scheduler.c
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
#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>
|
||||
#include <wayland-util.h>
|
||||
#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>
|
||||
|
||||
void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler) {
|
||||
scheduler->needs_frame = true;
|
||||
scheduler->impl->schedule_frame(scheduler);
|
||||
}
|
||||
|
||||
void wlr_frame_scheduler_destroy(struct wlr_frame_scheduler *scheduler) {
|
||||
if (scheduler == NULL) {
|
||||
return;
|
||||
}
|
||||
wl_list_remove(&scheduler->backend_needs_frame.link);
|
||||
scheduler->impl->destroy(scheduler);
|
||||
}
|
||||
|
||||
void wlr_frame_scheduler_emit_frame(struct wlr_frame_scheduler *scheduler) {
|
||||
if (!scheduler->needs_frame) {
|
||||
return;
|
||||
}
|
||||
scheduler->needs_frame = false;
|
||||
wl_signal_emit_mutable(&scheduler->events.frame, NULL);
|
||||
}
|
||||
|
||||
static void frame_scheduler_handle_needs_frame(struct wl_listener *listener, void *data) {
|
||||
struct wlr_frame_scheduler *scheduler = wl_container_of(listener, scheduler, backend_needs_frame);
|
||||
wlr_frame_scheduler_schedule_frame(scheduler);
|
||||
}
|
||||
|
||||
void wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler,
|
||||
const struct wlr_frame_scheduler_impl *impl, struct wlr_output *output) {
|
||||
assert(impl->schedule_frame);
|
||||
assert(impl->destroy);
|
||||
|
||||
*scheduler = (struct wlr_frame_scheduler){
|
||||
.impl = impl,
|
||||
.output = output,
|
||||
};
|
||||
wl_signal_init(&scheduler->events.frame);
|
||||
scheduler->backend_needs_frame.notify = frame_scheduler_handle_needs_frame;
|
||||
wl_signal_add(&output->events.needs_frame, &scheduler->backend_needs_frame);
|
||||
}
|
||||
|
||||
// This struct and its methods are a common base for frame schedulers that restart their render loop
|
||||
// via an idle event source, which fires "soon", instead of using a more complex schedule. Deferring
|
||||
// the frame to an idle event is a crude way of ensuring that work done after scheduling the frame
|
||||
// gets picked up by the renderer, rather than rendering happening inside the schedule call and
|
||||
// missing out on any immediately following updates.
|
||||
struct idle_frame_scheduler {
|
||||
struct wlr_frame_scheduler base;
|
||||
struct wl_event_source *idle;
|
||||
// Whether the render loop is already awake, i.e. whether frames from idle events should be
|
||||
// inhibited.
|
||||
bool frame_pending;
|
||||
};
|
||||
|
||||
static void idle_frame_scheduler_emit_frame(struct idle_frame_scheduler *scheduler) {
|
||||
scheduler->frame_pending = false;
|
||||
wlr_frame_scheduler_emit_frame(&scheduler->base);
|
||||
}
|
||||
|
||||
static void idle_frame_scheduler_handle_idle(void *data) {
|
||||
struct idle_frame_scheduler *scheduler = data;
|
||||
if (!scheduler->frame_pending) {
|
||||
idle_frame_scheduler_emit_frame(scheduler);
|
||||
}
|
||||
scheduler->idle = NULL;
|
||||
}
|
||||
|
||||
static void idle_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct idle_frame_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base);
|
||||
if (scheduler->idle != NULL || scheduler->frame_pending) {
|
||||
// Either we are already set up to restart the render loop or it is already running.
|
||||
return;
|
||||
}
|
||||
|
||||
struct wl_event_loop *loop = scheduler->base.output->event_loop;
|
||||
scheduler->idle = wl_event_loop_add_idle(loop, idle_frame_scheduler_handle_idle, scheduler);
|
||||
}
|
||||
|
||||
static void idle_frame_scheduler_set_frame_pending(struct idle_frame_scheduler *scheduler) {
|
||||
scheduler->frame_pending = true;
|
||||
if (scheduler->idle) {
|
||||
wl_event_source_remove(scheduler->idle);
|
||||
scheduler->idle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void idle_frame_scheduler_finish(struct idle_frame_scheduler *scheduler) {
|
||||
if (scheduler->idle) {
|
||||
wl_event_source_remove(scheduler->idle);
|
||||
}
|
||||
}
|
||||
|
||||
// The present scheduler builds on the idle_frame_scheduler's logic for restarting the render loop,
|
||||
// and drives the render loop using `wlr_output.events.present`.
|
||||
struct present_scheduler {
|
||||
struct idle_frame_scheduler base;
|
||||
struct wl_listener commit;
|
||||
struct wl_listener present;
|
||||
};
|
||||
|
||||
static void present_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct present_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base);
|
||||
idle_frame_scheduler_finish(&scheduler->base);
|
||||
wl_list_remove(&scheduler->commit.link);
|
||||
wl_list_remove(&scheduler->present.link);
|
||||
free(scheduler);
|
||||
}
|
||||
|
||||
static void present_scheduler_handle_commit(struct wl_listener *listener, void *data) {
|
||||
struct present_scheduler *scheduler = wl_container_of(listener, scheduler, commit);
|
||||
if (scheduler->base.base.output->enabled) {
|
||||
idle_frame_scheduler_set_frame_pending(&scheduler->base);
|
||||
}
|
||||
}
|
||||
|
||||
static void present_scheduler_handle_present(struct wl_listener *listener, void *data) {
|
||||
struct present_scheduler *scheduler = wl_container_of(listener, scheduler, present);
|
||||
struct wlr_output_event_present *present = data;
|
||||
if (present->presented) {
|
||||
idle_frame_scheduler_emit_frame(&scheduler->base);
|
||||
} else {
|
||||
scheduler->base.frame_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct wlr_frame_scheduler_impl present_scheduler_impl = {
|
||||
.schedule_frame = idle_frame_scheduler_schedule_frame,
|
||||
.destroy = present_scheduler_destroy,
|
||||
};
|
||||
|
||||
struct wlr_frame_scheduler *wlr_present_scheduler_create(struct wlr_output *output) {
|
||||
struct present_scheduler *scheduler = calloc(1, sizeof(*scheduler));
|
||||
if (!scheduler) {
|
||||
return NULL;
|
||||
}
|
||||
wlr_frame_scheduler_init(&scheduler->base.base, &present_scheduler_impl, output);
|
||||
scheduler->commit.notify = present_scheduler_handle_commit;
|
||||
wl_signal_add(&output->events.commit, &scheduler->commit);
|
||||
scheduler->present.notify = present_scheduler_handle_present;
|
||||
wl_signal_add(&output->events.present, &scheduler->present);
|
||||
return &scheduler->base.base;
|
||||
}
|
||||
|
||||
// This scheduler builds on idle_frame_scheduler and uses Wayland's frame callbacks for driving the
|
||||
// render loop.
|
||||
struct wl_scheduler {
|
||||
struct idle_frame_scheduler base;
|
||||
struct wl_callback *frame;
|
||||
struct wl_listener precommit;
|
||||
};
|
||||
|
||||
static void wl_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct wl_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base);
|
||||
idle_frame_scheduler_finish(&scheduler->base);
|
||||
wl_list_remove(&scheduler->precommit.link);
|
||||
wl_callback_destroy(scheduler->frame);
|
||||
free(scheduler);
|
||||
}
|
||||
|
||||
static void wl_scheduler_handle_frame(void *data, struct wl_callback *cb, uint32_t time) {
|
||||
struct wl_scheduler *scheduler = data;
|
||||
assert(scheduler->frame == cb);
|
||||
idle_frame_scheduler_emit_frame(&scheduler->base);
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener wl_scheduler_frame_listener = {
|
||||
.done = wl_scheduler_handle_frame,
|
||||
};
|
||||
|
||||
static void wl_scheduler_handle_precommit(struct wl_listener *listener, void *data) {
|
||||
struct wl_scheduler *scheduler = wl_container_of(listener, scheduler, precommit);
|
||||
struct wlr_output_event_precommit *precommit = data;
|
||||
if (!output_pending_enabled(precommit->output, precommit->state)) {
|
||||
return;
|
||||
}
|
||||
idle_frame_scheduler_set_frame_pending(&scheduler->base);
|
||||
if (scheduler->frame != NULL) {
|
||||
wl_callback_destroy(scheduler->frame);
|
||||
}
|
||||
struct wl_surface *surface = wlr_wl_output_get_surface(scheduler->base.base.output);
|
||||
scheduler->frame = wl_surface_frame(surface);
|
||||
wl_callback_add_listener(scheduler->frame, &wl_scheduler_frame_listener, scheduler);
|
||||
}
|
||||
|
||||
static const struct wlr_frame_scheduler_impl wl_scheduler_impl = {
|
||||
.schedule_frame = idle_frame_scheduler_schedule_frame,
|
||||
.destroy = wl_scheduler_destroy,
|
||||
};
|
||||
|
||||
static struct wlr_frame_scheduler *wl_scheduler_create(struct wlr_output *output) {
|
||||
if (!wlr_output_is_wl(output)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_scheduler *scheduler = calloc(1, sizeof(*scheduler));
|
||||
if (!scheduler) {
|
||||
return NULL;
|
||||
}
|
||||
wlr_frame_scheduler_init(&scheduler->base.base, &wl_scheduler_impl, output);
|
||||
scheduler->precommit.notify = wl_scheduler_handle_precommit;
|
||||
wl_signal_add(&output->events.precommit, &scheduler->precommit);
|
||||
|
||||
return &scheduler->base.base;
|
||||
}
|
||||
|
||||
#define DEFAULT_REFRESH (60 * 1000) // 60 Hz in mHz
|
||||
|
||||
struct interval_scheduler {
|
||||
struct idle_frame_scheduler base;
|
||||
struct wl_event_source *frame_timer;
|
||||
struct wl_listener commit;
|
||||
|
||||
int32_t frame_delay;
|
||||
};
|
||||
|
||||
static void interval_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) {
|
||||
struct interval_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base);
|
||||
idle_frame_scheduler_finish(&scheduler->base);
|
||||
wl_event_source_remove(scheduler->frame_timer);
|
||||
wl_list_remove(&scheduler->commit.link);
|
||||
free(scheduler);
|
||||
}
|
||||
|
||||
static void interval_scheduler_handle_commit(struct wl_listener *listener, void *data) {
|
||||
struct interval_scheduler *scheduler = wl_container_of(listener, scheduler, commit);
|
||||
struct wlr_output_event_commit *commit = data;
|
||||
struct wlr_output *output = commit->output;
|
||||
if (commit->state->committed & WLR_OUTPUT_STATE_MODE) {
|
||||
int32_t refresh = output->refresh ? output->refresh : DEFAULT_REFRESH;
|
||||
scheduler->frame_delay = 1000 * 1000 / refresh;
|
||||
}
|
||||
|
||||
if (output->enabled) {
|
||||
assert(scheduler->frame_delay != 0);
|
||||
wl_event_source_timer_update(scheduler->frame_timer, scheduler->frame_delay);
|
||||
idle_frame_scheduler_set_frame_pending(&scheduler->base);
|
||||
}
|
||||
}
|
||||
|
||||
static int interval_scheduler_handle_timer(void *data) {
|
||||
struct interval_scheduler *scheduler = data;
|
||||
idle_frame_scheduler_emit_frame(&scheduler->base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct wlr_frame_scheduler_impl interval_scheduler_impl = {
|
||||
.schedule_frame = idle_frame_scheduler_schedule_frame,
|
||||
.destroy = interval_scheduler_destroy,
|
||||
};
|
||||
|
||||
struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *output) {
|
||||
if (!wlr_output_is_headless(output)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct interval_scheduler *scheduler = calloc(1, sizeof(*scheduler));
|
||||
if (!scheduler) {
|
||||
return NULL;
|
||||
}
|
||||
wlr_frame_scheduler_init(&scheduler->base.base, &interval_scheduler_impl, output);
|
||||
|
||||
scheduler->frame_delay = 1000 * 1000 / DEFAULT_REFRESH;
|
||||
|
||||
scheduler->frame_timer = wl_event_loop_add_timer(output->event_loop,
|
||||
interval_scheduler_handle_timer, scheduler);
|
||||
|
||||
scheduler->commit.notify = interval_scheduler_handle_commit;
|
||||
wl_signal_add(&output->events.commit, &scheduler->commit);
|
||||
|
||||
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");
|
||||
return wl_scheduler_create(output);
|
||||
}
|
||||
|
||||
if (wlr_output_is_headless(output)) {
|
||||
return wlr_interval_scheduler_create(output);
|
||||
}
|
||||
|
||||
return wlr_present_scheduler_create(output);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue