Merge branch 'scheduling-2-better-this-time' into 'master'

Add new frame scheduler interface

See merge request wlroots/wlroots!4307
This commit is contained in:
Rose Hudson 2024-04-24 15:04:24 +00:00
commit a06c728a05
29 changed files with 507 additions and 153 deletions

View file

@ -41,6 +41,7 @@ wlr_files += files(
'wlr_data_control_v1.c',
'wlr_drm.c',
'wlr_export_dmabuf_v1.c',
'wlr_frame_scheduler.c',
'wlr_foreign_toplevel_management_v1.c',
'wlr_ext_foreign_toplevel_list_v1.c',
'wlr_fullscreen_shell_v1.c',

View file

@ -354,7 +354,6 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend,
wl_list_init(&output->cursors);
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);
@ -410,10 +409,6 @@ void wlr_output_destroy(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);
}
@ -669,12 +664,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);
@ -691,11 +680,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);
struct timespec now;
@ -747,37 +731,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);
@ -860,10 +813,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);
}

View file

@ -3,6 +3,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_presentation_time.h>
#include <wlr/util/transform.h>
#include "types/wlr_scene.h"
@ -180,7 +181,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);
}
}

View file

@ -6,6 +6,7 @@
#include <wlr/render/wlr_renderer.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_linux_dmabuf_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_scene.h>
@ -328,7 +329,7 @@ static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *dam
-scene_output->x, -scene_output->y);
scale_output_damage(&output_damage, scene_output->output->scale);
if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) {
wlr_output_schedule_frame(scene_output->output);
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
}
pixman_region32_fini(&output_damage);
}
@ -800,7 +801,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff
(int)round((lx - scene_output->x) * output_scale),
(int)round((ly - scene_output->y) * output_scale));
if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) {
wlr_output_schedule_frame(scene_output->output);
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
}
pixman_region32_fini(&output_damage);
}
@ -1299,7 +1300,7 @@ static void scene_node_output_update(struct wlr_scene_node *node,
static void scene_output_update_geometry(struct wlr_scene_output *scene_output,
bool force_update) {
wlr_damage_ring_add_whole(&scene_output->damage_ring);
wlr_output_schedule_frame(scene_output->output);
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
scene_node_output_update(&scene_output->scene->tree.node,
&scene_output->scene->outputs, NULL, force_update ? scene_output : NULL);
@ -1349,16 +1350,10 @@ static void scene_output_handle_damage(struct wl_listener *listener, void *data)
scene_output, output_damage);
struct wlr_output_event_damage *event = data;
if (wlr_damage_ring_add(&scene_output->damage_ring, event->damage)) {
wlr_output_schedule_frame(scene_output->output);
wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler);
}
}
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));
@ -1366,6 +1361,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);
@ -1399,9 +1400,6 @@ 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);
return scene_output;
@ -1429,12 +1427,12 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
}
wlr_addon_finish(&scene_output->addon);
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);
wl_array_release(&scene_output->render_list);
free(scene_output);
@ -1674,11 +1672,6 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry,
bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
const struct wlr_scene_output_state_options *options) {
if (!scene_output->output->needs_frame && !pixman_region32_not_empty(
&scene_output->pending_commit_damage)) {
return true;
}
bool ok = false;
struct wlr_output_state state;
wlr_output_state_init(&state);
@ -1970,7 +1963,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
if (debug_damage == 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);
}
return true;

293
types/wlr_frame_scheduler.c Normal file
View file

@ -0,0 +1,293 @@
#include <assert.h>
#include <backend/headless.h>
#include <stdlib.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/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);
}
}
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_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);
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;
}
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);
}