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:
Kenny Levinsen 2026-04-09 11:07:53 +00:00
commit 651f85cb81
34 changed files with 882 additions and 220 deletions

View file

@ -2081,10 +2081,6 @@ static void handle_page_flip(int fd, unsigned seq,
.flags = present_flags, .flags = present_flags,
}; };
wlr_output_send_present(&conn->output, &present_event); wlr_output_send_present(&conn->output, &present_event);
if (drm->session->active) {
wlr_output_send_frame(&conn->output);
}
} }
int handle_drm_event(int fd, uint32_t mask, void *data) { int handle_drm_event(int fd, uint32_t mask, void *data) {

View file

@ -22,15 +22,6 @@ static struct wlr_headless_output *headless_output_from_output(
return output; return output;
} }
static void output_update_refresh(struct wlr_headless_output *output,
int32_t refresh) {
if (refresh <= 0) {
refresh = HEADLESS_DEFAULT_REFRESH;
}
output->frame_delay = 1000000 / refresh;
}
static bool output_test(struct wlr_output *wlr_output, static bool output_test(struct wlr_output *wlr_output,
const struct wlr_output_state *state) { const struct wlr_output_state *state) {
uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE;
@ -55,25 +46,16 @@ static bool output_test(struct wlr_output *wlr_output,
static bool output_commit(struct wlr_output *wlr_output, static bool output_commit(struct wlr_output *wlr_output,
const struct wlr_output_state *state) { const struct wlr_output_state *state) {
struct wlr_headless_output *output =
headless_output_from_output(wlr_output);
if (!output_test(wlr_output, state)) { if (!output_test(wlr_output, state)) {
return false; return false;
} }
if (state->committed & WLR_OUTPUT_STATE_MODE) {
output_update_refresh(output, state->custom_mode.refresh);
}
if (output_pending_enabled(wlr_output, state)) { if (output_pending_enabled(wlr_output, state)) {
struct wlr_output_event_present present_event = { struct wlr_output_event_present present_event = {
.commit_seq = wlr_output->commit_seq + 1, .commit_seq = wlr_output->commit_seq + 1,
.presented = true, .presented = true,
}; };
output_defer_present(wlr_output, present_event); output_defer_present(wlr_output, present_event);
wl_event_source_timer_update(output->frame_timer, output->frame_delay);
} }
return true; return true;
@ -94,7 +76,6 @@ static void output_destroy(struct wlr_output *wlr_output) {
wlr_output_finish(wlr_output); wlr_output_finish(wlr_output);
wl_list_remove(&output->link); wl_list_remove(&output->link);
wl_event_source_remove(output->frame_timer);
free(output); free(output);
} }
@ -110,12 +91,6 @@ bool wlr_output_is_headless(const struct wlr_output *wlr_output) {
return wlr_output->impl == &output_impl; return wlr_output->impl == &output_impl;
} }
static int signal_frame(void *data) {
struct wlr_headless_output *output = data;
wlr_output_send_frame(&output->wlr_output);
return 0;
}
struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend,
unsigned int width, unsigned int height) { unsigned int width, unsigned int height) {
struct wlr_headless_backend *backend = struct wlr_headless_backend *backend =
@ -136,8 +111,6 @@ struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend,
wlr_output_init(wlr_output, &backend->backend, &output_impl, backend->event_loop, &state); wlr_output_init(wlr_output, &backend->backend, &output_impl, backend->event_loop, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
output_update_refresh(output, 0);
size_t output_num = ++last_output_num; size_t output_num = ++last_output_num;
char name[64]; char name[64];
@ -148,8 +121,6 @@ struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend,
snprintf(description, sizeof(description), "Headless output %zu", output_num); snprintf(description, sizeof(description), "Headless output %zu", output_num);
wlr_output_set_description(wlr_output, description); wlr_output_set_description(wlr_output, description);
output->frame_timer = wl_event_loop_add_timer(backend->event_loop, signal_frame, output);
wl_list_insert(&backend->outputs, &output->link); wl_list_insert(&backend->outputs, &output->link);
if (backend->started) { if (backend->started) {

View file

@ -581,6 +581,11 @@ bool wlr_backend_is_wl(const struct wlr_backend *b) {
return b->impl == &backend_impl; return b->impl == &backend_impl;
} }
bool wlr_wl_backend_has_presentation_time(struct wlr_backend *wlr_backend) {
struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend);
return backend->presentation != NULL;
}
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_wl_backend *wl = wl_container_of(listener, wl, event_loop_destroy); struct wlr_wl_backend *wl = wl_container_of(listener, wl, event_loop_destroy);
backend_destroy(&wl->backend); backend_destroy(&wl->backend);

View file

@ -72,8 +72,6 @@ static void surface_frame_callback(void *data, struct wl_callback *cb,
assert(output->frame_callback == cb); assert(output->frame_callback == cb);
wl_callback_destroy(cb); wl_callback_destroy(cb);
output->frame_callback = NULL; output->frame_callback = NULL;
wlr_output_send_frame(&output->wlr_output);
} }
static const struct wl_callback_listener frame_listener = { static const struct wl_callback_listener frame_listener = {

View file

@ -795,8 +795,6 @@ void handle_x11_present_event(struct wlr_x11_backend *x11,
}; };
timespec_from_nsec(&present_event.when, complete_notify->ust * 1000); timespec_from_nsec(&present_event.when, complete_notify->ust * 1000);
wlr_output_send_present(&output->wlr_output, &present_event); wlr_output_send_present(&output->wlr_output, &present_event);
wlr_output_send_frame(&output->wlr_output);
break; break;
default: default:
wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type); wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type);

View file

@ -8,6 +8,7 @@
#include <wlr/backend.h> #include <wlr/backend.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
@ -104,10 +105,10 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct output *output = calloc(1, sizeof(*output)); struct output *output = calloc(1, sizeof(*output));
output->wlr = wlr_output; output->wlr = wlr_output;
output->server = server; output->server = server;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output); output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
output->frame.notify = output_handle_frame;
wl_signal_add(&output->scene_output->frame_scheduler->events.frame, &output->frame);
struct wlr_output_state state; struct wlr_output_state state;
wlr_output_state_init(&state); wlr_output_state_init(&state);

View file

@ -10,6 +10,7 @@
#include <wlr/backend/wayland.h> #include <wlr/backend/wayland.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
@ -220,10 +221,10 @@ int main(int argc, char *argv[]) {
wl_subsurface_set_position(subsurface, 20, 20); wl_subsurface_set_position(subsurface, 20, 20);
struct wlr_output *output = wlr_wl_output_create_from_surface(backend, child_surface); struct wlr_output *output = wlr_wl_output_create_from_surface(backend, child_surface);
wlr_output_init_render(output, allocator, renderer); wlr_output_init_render(output, allocator, renderer);
scene_output = wlr_scene_output_create(scene, output);
scene_output = wlr_scene_output_create(scene, output);
output_frame.notify = output_handle_frame; output_frame.notify = output_handle_frame;
wl_signal_add(&output->events.frame, &output_frame); wl_signal_add(&scene_output->frame_scheduler->events.frame, &output_frame);
struct wlr_output_state state; struct wlr_output_state state;
wlr_output_state_init(&state); wlr_output_state_init(&state);

View file

@ -10,6 +10,7 @@
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_drm.h> #include <wlr/types/wlr_drm.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h> #include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layer.h> #include <wlr/types/wlr_output_layer.h>
@ -56,6 +57,7 @@ struct output {
struct wl_list link; struct wl_list link;
struct server *server; struct server *server;
struct wlr_output *wlr_output; struct wlr_output *wlr_output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_list surfaces; struct wl_list surfaces;
struct wl_listener frame; struct wl_listener frame;
@ -155,6 +157,8 @@ static void output_handle_frame(struct wl_listener *listener, void *data) {
} }
wl_array_release(&layers_arr); wl_array_release(&layers_arr);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
} }
static void server_handle_new_output(struct wl_listener *listener, void *data) { static void server_handle_new_output(struct wl_listener *listener, void *data) {
@ -167,10 +171,12 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) {
output->wlr_output = wlr_output; output->wlr_output = wlr_output;
output->server = server; output->server = server;
wl_list_init(&output->surfaces); wl_list_init(&output->surfaces);
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
wl_list_insert(&server->outputs, &output->link); wl_list_insert(&server->outputs, &output->link);
output->frame_scheduler = wlr_frame_scheduler_autocreate(wlr_output);
output->frame.notify = output_handle_frame;
wl_signal_add(&output->frame_scheduler->events.frame, &output->frame);
struct wlr_output_state state; struct wlr_output_state state;
wlr_output_state_init(&state); wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true); wlr_output_state_set_enabled(&state, true);
@ -181,6 +187,8 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) {
wlr_output_commit_state(wlr_output, &state); wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
wlr_output_create_global(wlr_output, server->wl_display); wlr_output_create_global(wlr_output, server->wl_display);
} }

View file

@ -12,6 +12,7 @@
#include <wlr/backend/session.h> #include <wlr/backend/session.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_layout.h>
@ -37,6 +38,7 @@ struct sample_state {
struct sample_output { struct sample_output {
struct sample_state *sample; struct sample_state *sample;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
}; };
@ -143,6 +145,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
wlr_render_pass_submit(pass); wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state); wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state); wlr_output_state_finish(&output_state);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
} }
static void update_velocities(struct sample_state *sample, static void update_velocities(struct sample_state *sample,
@ -157,6 +161,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
wlr_output_layout_remove(sample->layout, sample_output->output); wlr_output_layout_remove(sample->layout, sample_output->output);
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -170,7 +175,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
wlr_output_layout_add_auto(sample->layout, output); wlr_output_layout_add_auto(sample->layout, output);
sample_output->output = output; sample_output->output = output;
sample_output->sample = sample; sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame); sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -184,6 +190,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void keyboard_key_notify(struct wl_listener *listener, void *data) { static void keyboard_key_notify(struct wl_listener *listener, void *data) {

View file

@ -11,6 +11,7 @@
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_pointer.h> #include <wlr/types/wlr_pointer.h>
@ -62,6 +63,7 @@ struct touch_point {
struct sample_output { struct sample_output {
struct sample_state *state; struct sample_state *state;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
}; };
@ -115,6 +117,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
wlr_render_pass_submit(pass); wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state); wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state); wlr_output_state_finish(&output_state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void handle_cursor_motion(struct wl_listener *listener, void *data) { static void handle_cursor_motion(struct wl_listener *listener, void *data) {
@ -255,6 +259,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
wlr_output_layout_remove(sample->layout, sample_output->output); wlr_output_layout_remove(sample->layout, sample_output->output);
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -267,7 +272,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output; sample_output->output = output;
sample_output->state = sample; sample_output->state = sample;
wl_signal_add(&output->events.frame, &sample_output->frame); sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -282,6 +288,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }

View file

@ -11,6 +11,7 @@
#include <wlr/backend.h> #include <wlr/backend.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
@ -33,6 +34,7 @@ struct sample_state {
struct sample_output { struct sample_output {
struct sample_state *sample; struct sample_state *sample;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
float x_offs, y_offs; float x_offs, y_offs;
@ -93,6 +95,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
sample_output->y_offs = 0; sample_output->y_offs = 0;
} }
sample->last_frame = now; sample->last_frame = now;
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void update_velocities(struct sample_state *sample, static void update_velocities(struct sample_state *sample,
@ -108,6 +112,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -123,7 +128,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
sample_output->output = output; sample_output->output = output;
sample_output->sample = sample; sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame); sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -139,6 +145,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void keyboard_key_notify(struct wl_listener *listener, void *data) { static void keyboard_key_notify(struct wl_listener *listener, void *data) {

View file

@ -10,6 +10,7 @@
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
@ -74,10 +75,10 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct output *output = calloc(1, sizeof(*output)); struct output *output = calloc(1, sizeof(*output));
output->wlr = wlr_output; output->wlr = wlr_output;
output->server = server; output->server = server;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output); output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
output->frame.notify = output_handle_frame;
wl_signal_add(&output->scene_output->frame_scheduler->events.frame, &output->frame);
struct wlr_output_state state; struct wlr_output_state state;
wlr_output_state_init(&state); wlr_output_state_init(&state);

View file

@ -8,6 +8,7 @@
#include <wlr/backend/session.h> #include <wlr/backend/session.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
@ -28,6 +29,7 @@ struct sample_state {
struct sample_output { struct sample_output {
struct sample_state *sample; struct sample_state *sample;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
}; };
@ -78,6 +80,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
wlr_output_commit_state(wlr_output, &state); wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
sample->last_frame = now; sample->last_frame = now;
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void output_remove_notify(struct wl_listener *listener, void *data) { static void output_remove_notify(struct wl_listener *listener, void *data) {
@ -86,6 +90,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
wlr_log(WLR_DEBUG, "Output removed"); wlr_log(WLR_DEBUG, "Output removed");
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -99,7 +104,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output; sample_output->output = output;
sample_output->sample = sample; sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -113,6 +120,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void keyboard_key_notify(struct wl_listener *listener, void *data) { static void keyboard_key_notify(struct wl_listener *listener, void *data) {

View file

@ -11,6 +11,7 @@
#include <wlr/backend/session.h> #include <wlr/backend/session.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
@ -66,6 +67,7 @@ struct tablet_pad_state {
struct sample_output { struct sample_output {
struct sample_state *sample; struct sample_state *sample;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
}; };
@ -160,6 +162,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
wlr_output_commit_state(wlr_output, &output_state); wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state); wlr_output_state_finish(&output_state);
sample->last_frame = now; sample->last_frame = now;
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void tablet_tool_axis_notify(struct wl_listener *listener, void *data) { static void tablet_tool_axis_notify(struct wl_listener *listener, void *data) {
@ -261,6 +265,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -273,7 +278,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output; sample_output->output = output;
sample_output->sample = sample; sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -287,6 +294,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void keyboard_key_notify(struct wl_listener *listener, void *data) { static void keyboard_key_notify(struct wl_listener *listener, void *data) {

View file

@ -11,6 +11,7 @@
#include <wlr/backend/session.h> #include <wlr/backend/session.h>
#include <wlr/render/allocator.h> #include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h> #include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
@ -52,6 +53,7 @@ struct touch_state {
struct sample_output { struct sample_output {
struct sample_state *sample; struct sample_state *sample;
struct wlr_output *output; struct wlr_output *output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener destroy; struct wl_listener destroy;
}; };
@ -96,6 +98,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
wlr_output_commit_state(wlr_output, &output_state); wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state); wlr_output_state_finish(&output_state);
sample->last_frame = now; sample->last_frame = now;
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void touch_down_notify(struct wl_listener *listener, void *data) { static void touch_down_notify(struct wl_listener *listener, void *data) {
@ -164,6 +168,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link); wl_list_remove(&sample_output->destroy.link);
wlr_frame_scheduler_destroy(sample_output->frame_scheduler);
free(sample_output); free(sample_output);
} }
@ -176,7 +181,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output; sample_output->output = output;
sample_output->sample = sample; sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output);
wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify; sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy); wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify; sample_output->destroy.notify = output_remove_notify;
@ -190,6 +197,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) {
} }
wlr_output_commit_state(output, &state); wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler);
} }
static void keyboard_key_notify(struct wl_listener *listener, void *data) { static void keyboard_key_notify(struct wl_listener *listener, void *data) {

View file

@ -4,8 +4,6 @@
#include <wlr/backend/headless.h> #include <wlr/backend/headless.h>
#include <wlr/backend/interface.h> #include <wlr/backend/interface.h>
#define HEADLESS_DEFAULT_REFRESH (60 * 1000) // 60 Hz
struct wlr_headless_backend { struct wlr_headless_backend {
struct wlr_backend backend; struct wlr_backend backend;
struct wl_event_loop *event_loop; struct wl_event_loop *event_loop;
@ -19,9 +17,6 @@ struct wlr_headless_output {
struct wlr_headless_backend *backend; struct wlr_headless_backend *backend;
struct wl_list link; struct wl_list link;
struct wl_event_source *frame_timer;
int frame_delay; // ms
}; };
struct wlr_headless_backend *headless_backend_from_backend( struct wlr_headless_backend *headless_backend_from_backend(

View file

@ -67,6 +67,9 @@ struct wlr_vk_device {
struct wlr_drm_format_set dmabuf_render_formats; struct wlr_drm_format_set dmabuf_render_formats;
struct wlr_drm_format_set dmabuf_texture_formats; struct wlr_drm_format_set dmabuf_texture_formats;
struct wlr_drm_format_set shm_texture_formats; struct wlr_drm_format_set shm_texture_formats;
float timestamp_period;
uint32_t timestamp_valid_bits;
}; };
// Tries to find the VkPhysicalDevice for the given drm fd. // Tries to find the VkPhysicalDevice for the given drm fd.
@ -412,6 +415,12 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer);
// finished execution. // finished execution.
bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd); bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd);
struct wlr_vk_render_timer {
struct wlr_render_timer base;
struct wlr_vk_renderer *renderer;
VkQueryPool query_pool;
};
struct wlr_vk_render_pass_texture { struct wlr_vk_render_pass_texture {
struct wlr_vk_texture *texture; struct wlr_vk_texture *texture;
@ -436,6 +445,8 @@ struct wlr_vk_render_pass {
struct wlr_drm_syncobj_timeline *signal_timeline; struct wlr_drm_syncobj_timeline *signal_timeline;
uint64_t signal_point; uint64_t signal_point;
struct wlr_vk_render_timer *timer;
struct wl_array textures; // struct wlr_vk_render_pass_texture struct wl_array textures; // struct wlr_vk_render_pass_texture
}; };

View file

@ -48,6 +48,11 @@ struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend
*/ */
bool wlr_backend_is_wl(const struct wlr_backend *backend); bool wlr_backend_is_wl(const struct wlr_backend *backend);
/**
* Check whether the Wayland server advertises the wp_presentation protocol.
*/
bool wlr_wl_backend_has_presentation_time(struct wlr_backend *backend);
/** /**
* Check whether the provided input device is a Wayland input device. * Check whether the provided input device is a Wayland input device.
*/ */

View file

@ -0,0 +1,26 @@
/*
* This an unstable interface of wlroots. No guarantees are made regarding the
* future consistency of this API.
*/
#ifndef WLR_USE_UNSTABLE
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
#endif
#ifndef WLR_INTERFACES_WLR_FRAME_SCHEDULER_H
#define WLR_INTERFACES_WLR_FRAME_SCHEDULER_H
struct wlr_frame_scheduler;
struct wlr_output;
struct wlr_frame_scheduler_impl {
/**
* Ensure that the scheduler->frame signal will be fired in the future.
*/
void (*schedule_frame)(struct wlr_frame_scheduler *scheduler);
void (*destroy)(struct wlr_frame_scheduler *scheduler);
};
void wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler,
const struct wlr_frame_scheduler_impl *impl, struct wlr_output *output);
#endif

View file

@ -117,12 +117,6 @@ void wlr_output_finish(struct wlr_output *output);
* output changes. * output changes.
*/ */
void wlr_output_update_needs_frame(struct wlr_output *output); void wlr_output_update_needs_frame(struct wlr_output *output);
/**
* Send a frame event.
*
* See wlr_output.events.frame.
*/
void wlr_output_send_frame(struct wlr_output *output);
/** /**
* Send a present event. * Send a present event.
* *

View file

@ -0,0 +1,89 @@
/*
* This an unstable interface of wlroots. No guarantees are made regarding the
* future consistency of this API.
*/
#ifndef WLR_USE_UNSTABLE
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
#endif
#ifndef WLR_TYPES_WLR_FRAME_SCHEDULER_H
#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;
struct {
struct wl_signal frame;
} events;
// Whether the render loop should be kept awake. True if wlr_frame_scheduler_schedule_frame()
// was called since the last frame event.
bool needs_frame;
// private state
const struct wlr_frame_scheduler_impl *impl;
struct wl_listener backend_needs_frame;
};
/**
* The present scheduler maintains a render loop based on `wlr_output.events.present`. To wake the
* render loop, it emits the frame signal when the compositor's event loop is idle.
*/
struct wlr_frame_scheduler *wlr_present_scheduler_create(struct wlr_output *output);
/**
* The Wayland scheduler maintains a render loop based on wl_surface.frame callbacks. To wake the
* render loop, it emits the frame signal when the compositor's event loop is idle.
*/
struct wlr_frame_scheduler *wlr_wl_scheduler_create(struct wlr_output *output);
/**
* The interval scheduler maintains a render loop based on a timer. To wake the render loop, it
* emits the frame signal when the compositor's event loop is idle.
*/
struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *output);
/**
* Creates an appropriate frame scheduler for the given output's backend capabilities.
*/
struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output);
/**
* Inform the scheduler that a frame signal is needed. The scheduler implementation will choose a
* good time to emit the signal. The signal is emitted only if this function has been called at
* least once since the last signal.
*/
void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler);
/**
* Emits a frame signal if `wlr_frame_scheduler_schedule_frame()` has been called since the last
* frame signal.
*/
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

View file

@ -219,10 +219,6 @@ struct wlr_output {
// true, changes may either succeed or fail. // true, changes may either succeed or fail.
bool adaptive_sync_supported; bool adaptive_sync_supported;
bool needs_frame;
// damage for cursors and fullscreen surface, in output-local coordinates
bool frame_pending;
// true for example with VR headsets // true for example with VR headsets
bool non_desktop; bool non_desktop;
@ -230,8 +226,6 @@ struct wlr_output {
uint32_t commit_seq; uint32_t commit_seq;
struct { struct {
// Request to render a frame
struct wl_signal frame;
// Emitted when software cursors or backend-specific logic damage the // Emitted when software cursors or backend-specific logic damage the
// output // output
struct wl_signal damage; // struct wlr_output_event_damage struct wl_signal damage; // struct wlr_output_event_damage
@ -252,7 +246,6 @@ struct wlr_output {
struct wl_signal destroy; struct wl_signal destroy;
} events; } events;
struct wl_event_source *idle_frame;
struct wl_event_source *idle_done; struct wl_event_source *idle_done;
int attach_render_locks; // number of locks forcing rendering int attach_render_locks; // number of locks forcing rendering
@ -406,11 +399,6 @@ bool wlr_output_test_state(struct wlr_output *output,
*/ */
bool wlr_output_commit_state(struct wlr_output *output, bool wlr_output_commit_state(struct wlr_output *output,
const struct wlr_output_state *state); const struct wlr_output_state *state);
/**
* Manually schedules a `frame` event. If a `frame` event is already pending,
* it is a no-op.
*/
void wlr_output_schedule_frame(struct wlr_output *output);
/** /**
* Returns the maximum length of each gamma ramp, or 0 if unsupported. * Returns the maximum length of each gamma ramp, or 0 if unsupported.
*/ */

View file

@ -224,6 +224,7 @@ struct wlr_scene_output {
struct wlr_output *output; struct wlr_output *output;
struct wl_list link; // wlr_scene.outputs struct wl_list link; // wlr_scene.outputs
struct wlr_scene *scene; struct wlr_scene *scene;
struct wlr_frame_scheduler *frame_scheduler;
struct wlr_addon addon; struct wlr_addon addon;
struct wlr_damage_ring damage_ring; struct wlr_damage_ring damage_ring;
@ -258,7 +259,6 @@ struct wlr_scene_output {
struct wl_listener output_commit; struct wl_listener output_commit;
struct wl_listener output_damage; struct wl_listener output_damage;
struct wl_listener output_needs_frame;
struct wl_list damage_highlight_regions; struct wl_list damage_highlight_regions;
@ -268,12 +268,9 @@ struct wlr_scene_output {
uint64_t in_point; uint64_t in_point;
struct wlr_drm_syncobj_timeline *out_timeline; struct wlr_drm_syncobj_timeline *out_timeline;
uint64_t out_point; uint64_t out_point;
} WLR_PRIVATE;
};
struct wlr_scene_timer { struct wlr_render_timer *render_timer;
int64_t pre_render_duration; } WLR_PRIVATE;
struct wlr_render_timer *render_timer;
}; };
/** A layer shell scene helper */ /** A layer shell scene helper */
@ -592,10 +589,15 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output);
*/ */
void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, void wlr_scene_output_set_position(struct wlr_scene_output *scene_output,
int lx, int ly); int lx, int ly);
/**
* Replace the frame scheduler for this scene output, destroying the previous
* scheduler. If scene output currently needs a new frame, a frame will be
* scheduled on the new frame scheduler.
*/
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_output_state_options {
struct wlr_scene_timer *timer;
/** /**
* Color transform to apply before the output's color transform. Cannot be * Color transform to apply before the output's color transform. Cannot be
* used when the output has a non-NULL image description set. * used when the output has a non-NULL image description set.
@ -610,12 +612,6 @@ struct wlr_scene_output_state_options {
struct wlr_swapchain *swapchain; struct wlr_swapchain *swapchain;
}; };
/**
* Returns true if scene wants to render a new frame. False, if no new frame
* is needed and an output commit can be skipped for the current frame.
*/
bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output);
/** /**
* Render and commit an output. * Render and commit an output.
*/ */
@ -628,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, 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); 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 * 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 * wlr_scene_output_commit() for which wlr_scene_surface.primary_output

View file

@ -295,6 +295,11 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
vkCmdEndRenderPass(render_cb->vk); vkCmdEndRenderPass(render_cb->vk);
if (pass->timer != NULL) {
vkCmdWriteTimestamp(render_cb->vk, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
pass->timer->query_pool, 1);
}
size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture);
size_t render_wait_cap = (1 + pass_textures_len) * WLR_DMABUF_MAX_PLANES; size_t render_wait_cap = (1 + pass_textures_len) * WLR_DMABUF_MAX_PLANES;
render_wait = calloc(render_wait_cap, sizeof(*render_wait)); render_wait = calloc(render_wait_cap, sizeof(*render_wait));
@ -1280,6 +1285,14 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT);
} }
struct wlr_vk_render_timer *timer = NULL;
if (options != NULL && options->timer != NULL) {
timer = wl_container_of(options->timer, timer, base);
vkCmdResetQueryPool(cb->vk, timer->query_pool, 0, 2);
vkCmdWriteTimestamp(cb->vk, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
timer->query_pool, 0);
}
int width = buffer->wlr_buffer->width; int width = buffer->wlr_buffer->width;
int height = buffer->wlr_buffer->height; int height = buffer->wlr_buffer->height;
VkRect2D rect = { .extent = { width, height } }; VkRect2D rect = { .extent = { width, height } };
@ -1308,5 +1321,6 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
pass->render_buffer_out = buffer_out; pass->render_buffer_out = buffer_out;
pass->render_setup = render_setup; pass->render_setup = render_setup;
pass->command_buffer = cb; pass->command_buffer = cb;
pass->timer = timer;
return pass; return pass;
} }

View file

@ -1556,6 +1556,83 @@ static struct wlr_render_pass *vulkan_begin_buffer_pass(struct wlr_renderer *wlr
return &render_pass->base; return &render_pass->base;
} }
static const struct wlr_render_timer_impl render_timer_impl;
static struct wlr_render_timer *vulkan_render_timer_create(
struct wlr_renderer *wlr_renderer) {
struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
if (renderer->dev->timestamp_valid_bits == 0) {
wlr_log(WLR_ERROR, "Failed to create render timer: "
"timestamp queries not supported by queue family");
return NULL;
}
struct wlr_vk_render_timer *timer = calloc(1, sizeof(*timer));
if (!timer) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
VkQueryPoolCreateInfo pool_info = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.queryType = VK_QUERY_TYPE_TIMESTAMP,
.queryCount = 2,
};
VkResult res = vkCreateQueryPool(renderer->dev->dev, &pool_info,
NULL, &timer->query_pool);
if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateQueryPool", res);
free(timer);
return NULL;
}
timer->base.impl = &render_timer_impl;
timer->renderer = renderer;
return &timer->base;
}
static int vulkan_render_timer_get_duration_ns(
struct wlr_render_timer *wlr_timer) {
struct wlr_vk_render_timer *timer =
wl_container_of(wlr_timer, timer, base);
struct wlr_vk_renderer *renderer = timer->renderer;
// Layout: [ timestamp1, avail1, timestamp2, avail2 ]
uint64_t data[4] = {0};
VkResult res = vkGetQueryPoolResults(renderer->dev->dev,
timer->query_pool, 0, 2, sizeof(data), data,
2 * sizeof(uint64_t),
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
if (res == VK_NOT_READY || data[1] == 0 || data[3] == 0) {
wlr_log(WLR_ERROR, "Failed to get render duration: "
"timestamp query results not yet ready");
return -1;
}
if (res != VK_SUCCESS) {
wlr_vk_error("vkGetQueryPoolResults", res);
return -1;
}
uint64_t ticks = data[2] - data[0];
if (renderer->dev->timestamp_valid_bits < 64) {
ticks &= (1ULL << renderer->dev->timestamp_valid_bits) - 1;
}
return (int)(ticks * renderer->dev->timestamp_period);
}
static void vulkan_render_timer_destroy(
struct wlr_render_timer *wlr_timer) {
struct wlr_vk_render_timer *timer =
wl_container_of(wlr_timer, timer, base);
vkDestroyQueryPool(timer->renderer->dev->dev, timer->query_pool, NULL);
free(timer);
}
static const struct wlr_render_timer_impl render_timer_impl = {
.get_duration_ns = vulkan_render_timer_get_duration_ns,
.destroy = vulkan_render_timer_destroy,
};
static const struct wlr_renderer_impl renderer_impl = { static const struct wlr_renderer_impl renderer_impl = {
.get_texture_formats = vulkan_get_texture_formats, .get_texture_formats = vulkan_get_texture_formats,
.get_render_formats = vulkan_get_render_formats, .get_render_formats = vulkan_get_render_formats,
@ -1563,6 +1640,7 @@ static const struct wlr_renderer_impl renderer_impl = {
.get_drm_fd = vulkan_get_drm_fd, .get_drm_fd = vulkan_get_drm_fd,
.texture_from_buffer = vulkan_texture_from_buffer, .texture_from_buffer = vulkan_texture_from_buffer,
.begin_buffer_pass = vulkan_begin_buffer_pass, .begin_buffer_pass = vulkan_begin_buffer_pass,
.render_timer_create = vulkan_render_timer_create,
}; };
// Initializes the VkDescriptorSetLayout and VkPipelineLayout needed // Initializes the VkDescriptorSetLayout and VkPipelineLayout needed

View file

@ -488,10 +488,15 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini,
graphics_found = queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT; graphics_found = queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT;
if (graphics_found) { if (graphics_found) {
dev->queue_family = i; dev->queue_family = i;
dev->timestamp_valid_bits = queue_props[i].timestampValidBits;
break; break;
} }
} }
assert(graphics_found); assert(graphics_found);
VkPhysicalDeviceProperties phdev_props;
vkGetPhysicalDeviceProperties(phdev, &phdev_props);
dev->timestamp_period = phdev_props.limits.timestampPeriod;
} }
bool exportable_semaphore = false, importable_semaphore = false; bool exportable_semaphore = false, importable_semaphore = false;

View file

@ -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) { void wlr_render_timer_destroy(struct wlr_render_timer *timer) {
if (!timer) {
return;
}
if (!timer->impl->destroy) { if (!timer->impl->destroy) {
return; return;
} }

View file

@ -12,6 +12,7 @@
#include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
@ -74,6 +75,7 @@ struct tinywl_output {
struct wl_list link; struct wl_list link;
struct tinywl_server *server; struct tinywl_server *server;
struct wlr_output *wlr_output; struct wlr_output *wlr_output;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_listener frame; struct wl_listener frame;
struct wl_listener request_state; struct wl_listener request_state;
struct wl_listener destroy; struct wl_listener destroy;
@ -603,6 +605,7 @@ static void output_destroy(struct wl_listener *listener, void *data) {
wl_list_remove(&output->request_state.link); wl_list_remove(&output->request_state.link);
wl_list_remove(&output->destroy.link); wl_list_remove(&output->destroy.link);
wl_list_remove(&output->link); wl_list_remove(&output->link);
wlr_frame_scheduler_destroy(output->frame_scheduler);
free(output); free(output);
} }
@ -641,10 +644,6 @@ static void server_new_output(struct wl_listener *listener, void *data) {
output->wlr_output = wlr_output; output->wlr_output = wlr_output;
output->server = server; output->server = server;
/* Sets up a listener for the frame event. */
output->frame.notify = output_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
/* Sets up a listener for the state request event. */ /* Sets up a listener for the state request event. */
output->request_state.notify = output_request_state; output->request_state.notify = output_request_state;
wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_signal_add(&wlr_output->events.request_state, &output->request_state);
@ -668,6 +667,10 @@ static void server_new_output(struct wl_listener *listener, void *data) {
wlr_output); wlr_output);
struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output); struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output);
wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output);
/* Sets up a listener for the frame event. */
output->frame.notify = output_frame;
wl_signal_add(&scene_output->frame_scheduler->events.frame, &output->frame);
} }
static void xdg_toplevel_map(struct wl_listener *listener, void *data) { static void xdg_toplevel_map(struct wl_listener *listener, void *data) {

View file

@ -5,6 +5,7 @@
#include <wlr/interfaces/wlr_ext_image_capture_source_v1.h> #include <wlr/interfaces/wlr_ext_image_capture_source_v1.h>
#include <wlr/interfaces/wlr_output.h> #include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_ext_image_copy_capture_v1.h> #include <wlr/types/wlr_ext_image_copy_capture_v1.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "types/wlr_output.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, static void source_request_frame(struct wlr_ext_image_capture_source_v1 *base,
bool schedule_frame) { bool schedule_frame) {
struct scene_node_source *source = wl_container_of(base, source, base); struct scene_node_source *source = wl_container_of(base, source, base);
if (source->output.frame_pending) { if (schedule_frame && source->scene_output != NULL) {
wlr_output_send_frame(&source->output); wlr_frame_scheduler_schedule_frame(source->scene_output->frame_scheduler);
}
if (schedule_frame) {
wlr_output_update_needs_frame(&source->output);
} }
} }
@ -246,6 +244,12 @@ static bool output_commit(struct wlr_output *output, const struct wlr_output_sta
pixman_region32_fini(&full_damage); 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; return true;
} }
@ -283,11 +287,7 @@ static void source_handle_output_frame(struct wl_listener *listener, void *data)
return; return;
} }
if (!wlr_scene_output_needs_frame(source->scene_output)) { // Only render when there's actual damage to commit
return;
}
// We can only emit frames with damage
if (!pixman_region32_empty(&source->scene_output->pending_commit_damage)) { if (!pixman_region32_empty(&source->scene_output->pending_commit_damage)) {
source_render(source); 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); wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy);
source->output_frame.notify = source_handle_output_frame; 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; return &source->base;
} }

View file

@ -50,6 +50,7 @@ wlr_files += files(
'wlr_export_dmabuf_v1.c', 'wlr_export_dmabuf_v1.c',
'wlr_ext_background_effect_v1.c', 'wlr_ext_background_effect_v1.c',
'wlr_ext_data_control_v1.c', 'wlr_ext_data_control_v1.c',
'wlr_frame_scheduler.c',
'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c',
'wlr_ext_image_copy_capture_v1.c', 'wlr_ext_image_copy_capture_v1.c',
'wlr_ext_workspace_v1.c', 'wlr_ext_workspace_v1.c',

View file

@ -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->layers);
wl_list_init(&output->resources); wl_list_init(&output->resources);
wl_signal_init(&output->events.frame);
wl_signal_init(&output->events.damage); wl_signal_init(&output->events.damage);
wl_signal_init(&output->events.needs_frame); wl_signal_init(&output->events.needs_frame);
wl_signal_init(&output->events.precommit); 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); wl_signal_emit_mutable(&output->events.destroy, output);
wlr_addon_set_finish(&output->addons); 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.damage.listener_list));
assert(wl_list_empty(&output->events.needs_frame.listener_list)); assert(wl_list_empty(&output->events.needs_frame.listener_list));
assert(wl_list_empty(&output->events.precommit.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); wlr_swapchain_destroy(output->swapchain);
if (output->idle_frame != NULL) {
wl_event_source_remove(output->idle_frame);
}
if (output->idle_done != NULL) { if (output->idle_done != NULL) {
wl_event_source_remove(output->idle_done); 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; 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; struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &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) { void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state) {
output->commit_seq++; output->commit_seq++;
if (output_pending_enabled(output, state)) {
output->frame_pending = true;
output->needs_frame = false;
}
output_apply_state(output, state); output_apply_state(output, state);
} }
@ -844,37 +828,6 @@ bool wlr_output_commit_state(struct wlr_output *output,
return true; 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, void wlr_output_send_present(struct wlr_output *output,
struct wlr_output_event_present *event) { struct wlr_output_event_present *event) {
assert(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) { 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); wl_signal_emit_mutable(&output->events.needs_frame, output);
} }

View file

@ -6,6 +6,7 @@
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_fractional_scale_v1.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_linux_drm_syncobj_v1.h>
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_presentation_time.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) && if (!wl_list_empty(&surface->surface->current.frame_callback_list) &&
surface->buffer->primary_output != NULL && enabled) { 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

@ -8,6 +8,7 @@
#include <wlr/types/wlr_color_management_v1.h> #include <wlr/types/wlr_color_management_v1.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_damage_ring.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_gamma_control_v1.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h> #include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <wlr/types/wlr_presentation_time.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); pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height);
if (!pixman_region32_empty(&clipped)) { 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); wlr_damage_ring_add(&scene_output->damage_ring, &clipped);
pixman_region32_union(&scene_output->pending_commit_damage, 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; output->gamma_lut = event->control;
wlr_color_transform_unref(output->gamma_lut_color_transform); wlr_color_transform_unref(output->gamma_lut_color_transform);
output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control); 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, 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 && if (scene_output->scene->debug_damage_option == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT &&
!wl_list_empty(&scene_output->damage_highlight_regions)) { !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 // 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); 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_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
struct wlr_output *output) { struct wlr_output *output) {
struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_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; 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->output = output;
scene_output->scene = scene; scene_output->scene = scene;
wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); 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; scene_output->output_damage.notify = scene_output_handle_damage;
wl_signal_add(&output->events.damage, &scene_output->output_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_update_geometry(scene_output, false);
scene_output->render_timer = wlr_render_timer_create(output->renderer);
return scene_output; return scene_output;
} }
@ -1792,6 +1792,16 @@ static void highlight_region_destroy(struct highlight_region *damage) {
free(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) { void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
if (scene_output == NULL) { if (scene_output == NULL) {
return; return;
@ -1810,12 +1820,13 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
} }
wlr_addon_finish(&scene_output->addon); 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); wlr_damage_ring_finish(&scene_output->damage_ring);
pixman_region32_fini(&scene_output->pending_commit_damage); pixman_region32_fini(&scene_output->pending_commit_damage);
wl_list_remove(&scene_output->link); wl_list_remove(&scene_output->link);
wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_commit.link);
wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_damage.link);
wl_list_remove(&scene_output->output_needs_frame.link);
if (scene_output->in_timeline != NULL) { if (scene_output->in_timeline != NULL) {
wlr_drm_syncobj_timeline_signal(scene_output->in_timeline, UINT64_MAX); wlr_drm_syncobj_timeline_signal(scene_output->in_timeline, UINT64_MAX);
wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); 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; 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, bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
const struct wlr_scene_output_state_options *options) { const struct wlr_scene_output_state_options *options) {
if (!wlr_scene_output_needs_frame(scene_output)) {
return true;
}
bool ok = false; bool ok = false;
struct wlr_output_state state; struct wlr_output_state state;
wlr_output_state_init(&state); wlr_output_state_init(&state);
@ -2281,13 +2282,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
if (!options) { if (!options) {
options = &default_options; options = &default_options;
} }
struct wlr_scene_timer *timer = options->timer;
struct timespec start_time; struct timespec start_time;
if (timer) { clock_gettime(CLOCK_MONOTONIC, &start_time);
clock_gettime(CLOCK_MONOTONIC, &start_time);
wlr_scene_timer_finish(timer);
*timer = (struct wlr_scene_timer){0};
}
if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) {
// if the state is being disabled, do nothing. // 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) { if (scanout) {
scene_output_state_attempt_gamma(scene_output, state); scene_output_state_attempt_gamma(scene_output, state);
if (timer) { struct timespec end_time, duration;
struct timespec end_time, duration; clock_gettime(CLOCK_MONOTONIC, &end_time);
clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_sub(&duration, &end_time, &start_time);
timespec_sub(&duration, &end_time, &start_time); wlr_frame_scheduler_inform_render(scene_output->frame_scheduler,
timer->pre_render_duration = timespec_to_nsec(&duration); timespec_to_nsec(&duration), NULL);
}
return true; 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); assert(buffer->width == resolution_width && buffer->height == resolution_height);
if (timer) { int64_t pre_render_duration_ns;
timer->render_timer = wlr_render_timer_create(output->renderer); {
struct timespec end_time, duration; struct timespec end_time, duration;
clock_gettime(CLOCK_MONOTONIC, &end_time); clock_gettime(CLOCK_MONOTONIC, &end_time);
timespec_sub(&duration, &end_time, &start_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 if ((render_gamma_lut
@ -2484,7 +2478,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
scene_output->in_point++; scene_output->in_point++;
struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer,
&(struct wlr_buffer_pass_options){ &(struct wlr_buffer_pass_options){
.timer = timer ? timer->render_timer : NULL, .timer = scene_output->render_timer,
.color_transform = scene_output->combined_color_transform, .color_transform = scene_output->combined_color_transform,
.signal_timeline = scene_output->in_timeline, .signal_timeline = scene_output->in_timeline,
.signal_point = scene_output->in_point, .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); 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; 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, static void scene_node_send_frame_done(struct wlr_scene_node *node,
struct wlr_scene_output *scene_output, struct timespec *now) { struct wlr_scene_output *scene_output, struct timespec *now) {
if (!node->enabled) { if (!node->enabled) {

502
types/wlr_frame_scheduler.c Normal file
View 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);
}