diff --git a/include/sway/commands.h b/include/sway/commands.h index 5d468a421..45b5b0f4c 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -145,6 +145,7 @@ sway_cmd cmd_kill; sway_cmd cmd_layout; sway_cmd cmd_log_colors; sway_cmd cmd_mark; +sway_cmd cmd_max_render_time; sway_cmd cmd_mode; sway_cmd cmd_mouse_warping; sway_cmd cmd_move; diff --git a/include/sway/output.h b/include/sway/output.h index 741f5b5e7..ddc080229 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -72,7 +72,7 @@ struct sway_output *output_get_in_direction(struct sway_output *reference, void output_add_workspace(struct sway_output *output, struct sway_workspace *workspace); -typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, +typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box, float rotation, void *user_data); diff --git a/include/sway/surface.h b/include/sway/surface.h index 06874af2d..4da96c027 100644 --- a/include/sway/surface.h +++ b/include/sway/surface.h @@ -6,6 +6,13 @@ struct sway_surface { struct wlr_surface *wlr_surface; struct wl_listener destroy; + + /** + * This timer can be used for issuing delayed frame done callbacks (for + * example, to improve presentation latency). Its handler is set to a + * function that issues a frame done callback to this surface. + */ + struct wl_event_source *frame_done_timer; }; #endif diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 44cd4a7bc..29c87967e 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -108,6 +108,8 @@ struct sway_view { } events; struct wl_listener surface_new_subsurface; + + int max_render_time; // In milliseconds }; struct sway_xdg_shell_view { diff --git a/sway/commands.c b/sway/commands.c index e2c43e9fe..e7f1eafe7 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -119,6 +119,7 @@ static struct cmd_handler command_handlers[] = { { "kill", cmd_kill }, { "layout", cmd_layout }, { "mark", cmd_mark }, + { "max_render_time", cmd_max_render_time }, { "move", cmd_move }, { "nop", cmd_nop }, { "opacity", cmd_opacity }, diff --git a/sway/commands/max_render_time.c b/sway/commands/max_render_time.c new file mode 100644 index 000000000..ec3282f18 --- /dev/null +++ b/sway/commands/max_render_time.c @@ -0,0 +1,32 @@ +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/tree/view.h" + +struct cmd_results *cmd_max_render_time(int argc, char **argv) { + if (!argc) { + return cmd_results_new(CMD_INVALID, "Missing max render time argument."); + } + + int max_render_time; + if (!strcmp(*argv, "off")) { + max_render_time = 0; + } else { + char *end; + max_render_time = strtol(*argv, &end, 10); + if (*end || max_render_time <= 0) { + return cmd_results_new(CMD_INVALID, "Invalid max render time."); + } + } + + struct sway_container *container = config->handler_context.container; + if (!container || !container->view) { + return cmd_results_new(CMD_INVALID, + "Only views can have a max_render_time"); + } + + struct sway_view *view = container->view; + view->max_render_time = max_render_time; + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 0f715c19d..6498198b9 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -23,6 +23,7 @@ #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" +#include "sway/surface.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" @@ -80,6 +81,7 @@ struct surface_iterator_data { void *user_data; struct sway_output *output; + struct sway_view *view; double ox, oy; int width, height; float rotation; @@ -134,7 +136,7 @@ static void output_for_each_surface_iterator(struct wlr_surface *surface, return; } - data->user_iterator(data->output, surface, &box, data->rotation, + data->user_iterator(data->output, data->view, surface, &box, data->rotation, data->user_data); } @@ -145,6 +147,7 @@ void output_surface_for_each_surface(struct sway_output *output, .user_iterator = iterator, .user_data = user_data, .output = output, + .view = NULL, .ox = ox, .oy = oy, .width = surface->current.width, @@ -163,6 +166,7 @@ void output_view_for_each_surface(struct sway_output *output, .user_iterator = iterator, .user_data = user_data, .output = output, + .view = view, .ox = view->container->surface_x - output->lx - view->geometry.x, .oy = view->container->surface_y - output->ly @@ -182,6 +186,7 @@ void output_view_for_each_popup(struct sway_output *output, .user_iterator = iterator, .user_data = user_data, .output = output, + .view = view, .ox = view->container->surface_x - output->lx - view->geometry.x, .oy = view->container->surface_y - output->ly @@ -224,6 +229,7 @@ void output_layer_for_each_surface(struct sway_output *output, .user_iterator = iterator, .user_data = user_data, .output = output, + .view = NULL, .ox = popup_sx, .oy = popup_sy, .width = surface->current.width, @@ -291,6 +297,7 @@ static void output_for_each_surface(struct sway_output *output, .user_iterator = iterator, .user_data = user_data, .output = output, + .view = NULL, }; struct sway_workspace *workspace = output_get_active_workspace(output); @@ -401,18 +408,37 @@ bool output_has_opaque_overlay_layer_surface(struct sway_output *output) { return false; } -static void send_frame_done_iterator(struct sway_output *output, +struct send_frame_done_data { + struct timespec when; + int msec_until_refresh; +}; + +static void send_frame_done_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box, float rotation, - void *data) { - struct timespec *when = data; - wlr_surface_send_frame_done(surface, when); + void *user_data) { + int view_max_render_time = 0; + if (view != NULL) { + view_max_render_time = view->max_render_time; + } + + struct send_frame_done_data *data = user_data; + + int delay = data->msec_until_refresh - output->max_render_time + - view_max_render_time; + + if (output->max_render_time == 0 || view_max_render_time == 0 || delay < 1) { + wlr_surface_send_frame_done(surface, &data->when); + } else { + struct sway_surface *sway_surface = surface->data; + wl_event_source_timer_update(sway_surface->frame_done_timer, delay); + } } -static void send_frame_done(struct sway_output *output, struct timespec *when) { - output_for_each_surface(output, send_frame_done_iterator, when); +static void send_frame_done(struct sway_output *output, struct send_frame_done_data *data) { + output_for_each_surface(output, send_frame_done_iterator, data); } -static void count_surface_iterator(struct sway_output *output, +static void count_surface_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *_box, float rotation, void *data) { size_t *n = data; @@ -533,7 +559,7 @@ int output_repaint_timer_handler(void *data) { return 0; } -static void damage_handle_frame(struct wl_listener *listener, void *data) { +static void damage_handle_frame(struct wl_listener *listener, void *user_data) { struct sway_output *output = wl_container_of(listener, output, damage_frame); if (!output->enabled || !output->wlr_output->enabled) { @@ -592,9 +618,10 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) { } // Send frame done to all visible surfaces - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - send_frame_done(output, &now); + struct send_frame_done_data data = {0}; + clock_gettime(CLOCK_MONOTONIC, &data.when); + data.msec_until_refresh = msec_until_refresh; + send_frame_done(output, &data); } void output_damage_whole(struct sway_output *output) { @@ -605,7 +632,7 @@ void output_damage_whole(struct sway_output *output) { } } -static void damage_surface_iterator(struct sway_output *output, +static void damage_surface_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *_box, float rotation, void *_data) { bool *data = _data; @@ -811,7 +838,7 @@ static void handle_scale(struct wl_listener *listener, void *data) { update_output_manager_config(output->server); } -static void send_presented_iterator(struct sway_output *output, +static void send_presented_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box, float rotation, void *data) { struct wlr_presentation_event *event = data; diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 916d4eba1..c432b4769 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -97,7 +97,7 @@ damage_finish: pixman_region32_fini(&damage); } -static void render_surface_iterator(struct sway_output *output, +static void render_surface_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *_box, float rotation, void *_data) { struct render_data *data = _data; @@ -214,11 +214,11 @@ static void render_view_toplevels(struct sway_view *view, render_surface_iterator, &data); } -static void render_popup_iterator(struct sway_output *output, +static void render_popup_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box, float rotation, void *data) { // Render this popup's surface - render_surface_iterator(output, surface, box, rotation, data); + render_surface_iterator(output, view, surface, box, rotation, data); // Render this popup's child toplevels output_surface_for_each_surface(output, surface, box->x, box->y, diff --git a/sway/desktop/surface.c b/sway/desktop/surface.c index 41d4ce3fc..853c403d5 100644 --- a/sway/desktop/surface.c +++ b/sway/desktop/surface.c @@ -1,4 +1,6 @@ +#define _POSIX_C_SOURCE 200112L #include +#include #include #include "sway/server.h" #include "sway/surface.h" @@ -9,9 +11,21 @@ void handle_destroy(struct wl_listener *listener, void *data) { surface->wlr_surface->data = NULL; wl_list_remove(&surface->destroy.link); + wl_event_source_remove(surface->frame_done_timer); + free(surface); } +static int surface_frame_done_timer_handler(void *data) { + struct sway_surface *surface = data; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_surface_send_frame_done(surface->wlr_surface, &now); + + return 0; +} + void handle_compositor_new_surface(struct wl_listener *listener, void *data) { struct wlr_surface *wlr_surface = data; @@ -21,4 +35,7 @@ void handle_compositor_new_surface(struct wl_listener *listener, void *data) { surface->destroy.notify = handle_destroy; wl_signal_add(&wlr_surface->events.destroy, &surface->destroy); + + surface->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop, + surface_frame_done_timer_handler, surface); } diff --git a/sway/meson.build b/sway/meson.build index 125401545..e285c09ed 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -68,6 +68,7 @@ sway_sources = files( 'commands/inhibit_idle.c', 'commands/kill.c', 'commands/mark.c', + 'commands/max_render_time.c', 'commands/opacity.c', 'commands/include.c', 'commands/input.c', diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index 01496f194..858e73c10 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -131,6 +131,9 @@ must be separated by one space. For example: . Start with *max_render_time 1*. Increment by *1* if you see frame drops. + To achieve even lower latency, see the *max_render_time* surface + property in *sway*(5). + # SEE ALSO *sway*(5) *sway-input*(5) diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 9119b379a..52ee9d28e 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -182,6 +182,22 @@ set|plus|minus *layout* toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]... Cycles the layout mode of the focused container through a list of layouts. +*max_render_time* off| + Works together with *output max_render_time* to reduce the latency even + further by delaying the frame callbacks sent to a surface. When set to + a positive number of milliseconds, delays the frame callback in such a + way that the surface has the specified number of milliseconds to render + and commit new contents before being sampled by the compositor for the + next presentation. See *max_render_time* in *sway-output*(5) for + further details. + + To set this up for optimal latency: + . Set up *output max_render_time*. + . Put the target application in _full-screen_ and have it continuously + render something. + . Start by setting *max_render_time 1*. If the application drops + frames, increment by *1*. + *move* left|right|up|down [ px] Moves the focused container in the direction specified. If the container, the optional _px_ argument specifies how many pixels to move the container.