aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/sway/commands.h1
-rw-r--r--include/sway/config.h1
-rw-r--r--include/sway/output.h7
-rw-r--r--sway/commands/output.c1
-rw-r--r--sway/commands/output/max_render_time.c28
-rw-r--r--sway/config/output.c33
-rw-r--r--sway/desktop/output.c91
-rw-r--r--sway/meson.build1
-rw-r--r--sway/sway-output.5.scd24
-rw-r--r--sway/tree/output.c1
10 files changed, 166 insertions, 22 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h
index d994ae9b..5d468a42 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -271,6 +271,7 @@ sway_cmd output_cmd_background;
271sway_cmd output_cmd_disable; 271sway_cmd output_cmd_disable;
272sway_cmd output_cmd_dpms; 272sway_cmd output_cmd_dpms;
273sway_cmd output_cmd_enable; 273sway_cmd output_cmd_enable;
274sway_cmd output_cmd_max_render_time;
274sway_cmd output_cmd_mode; 275sway_cmd output_cmd_mode;
275sway_cmd output_cmd_position; 276sway_cmd output_cmd_position;
276sway_cmd output_cmd_scale; 277sway_cmd output_cmd_scale;
diff --git a/include/sway/config.h b/include/sway/config.h
index 6892c946..457e0a98 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -212,6 +212,7 @@ struct output_config {
212 float scale; 212 float scale;
213 int32_t transform; 213 int32_t transform;
214 enum wl_output_subpixel subpixel; 214 enum wl_output_subpixel subpixel;
215 int max_render_time; // In milliseconds
215 216
216 char *background; 217 char *background;
217 char *background_option; 218 char *background_option;
diff --git a/include/sway/output.h b/include/sway/output.h
index 7d7057e1..741f5b5e 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -51,6 +51,11 @@ struct sway_output {
51 struct { 51 struct {
52 struct wl_signal destroy; 52 struct wl_signal destroy;
53 } events; 53 } events;
54
55 struct timespec last_presentation;
56 uint32_t refresh_nsec;
57 int max_render_time; // In milliseconds
58 struct wl_event_source *repaint_timer;
54}; 59};
55 60
56struct sway_output *output_create(struct wlr_output *wlr_output); 61struct sway_output *output_create(struct wlr_output *wlr_output);
@@ -71,6 +76,8 @@ typedef void (*sway_surface_iterator_func_t)(struct sway_output *output,
71 struct wlr_surface *surface, struct wlr_box *box, float rotation, 76 struct wlr_surface *surface, struct wlr_box *box, float rotation,
72 void *user_data); 77 void *user_data);
73 78
79int output_repaint_timer_handler(void *data);
80
74void output_damage_whole(struct sway_output *output); 81void output_damage_whole(struct sway_output *output);
75 82
76void output_damage_surface(struct sway_output *output, double ox, double oy, 83void output_damage_surface(struct sway_output *output, double ox, double oy,
diff --git a/sway/commands/output.c b/sway/commands/output.c
index 59bc598c..db2acb50 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -12,6 +12,7 @@ static struct cmd_handler output_handlers[] = {
12 { "disable", output_cmd_disable }, 12 { "disable", output_cmd_disable },
13 { "dpms", output_cmd_dpms }, 13 { "dpms", output_cmd_dpms },
14 { "enable", output_cmd_enable }, 14 { "enable", output_cmd_enable },
15 { "max_render_time", output_cmd_max_render_time },
15 { "mode", output_cmd_mode }, 16 { "mode", output_cmd_mode },
16 { "pos", output_cmd_position }, 17 { "pos", output_cmd_position },
17 { "position", output_cmd_position }, 18 { "position", output_cmd_position },
diff --git a/sway/commands/output/max_render_time.c b/sway/commands/output/max_render_time.c
new file mode 100644
index 00000000..2d3cebe3
--- /dev/null
+++ b/sway/commands/output/max_render_time.c
@@ -0,0 +1,28 @@
1#include <strings.h>
2#include "sway/commands.h"
3#include "sway/config.h"
4
5struct cmd_results *output_cmd_max_render_time(int argc, char **argv) {
6 if (!config->handler_context.output_config) {
7 return cmd_results_new(CMD_FAILURE, "Missing output config");
8 }
9 if (!argc) {
10 return cmd_results_new(CMD_INVALID, "Missing max render time argument.");
11 }
12
13 int max_render_time;
14 if (!strcmp(*argv, "off")) {
15 max_render_time = 0;
16 } else {
17 char *end;
18 max_render_time = strtol(*argv, &end, 10);
19 if (*end || max_render_time <= 0) {
20 return cmd_results_new(CMD_INVALID, "Invalid max render time.");
21 }
22 }
23 config->handler_context.output_config->max_render_time = max_render_time;
24
25 config->handler_context.leftovers.argc = argc - 1;
26 config->handler_context.leftovers.argv = argv + 1;
27 return NULL;
28}
diff --git a/sway/config/output.c b/sway/config/output.c
index 25956fac..1d5f81da 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -47,6 +47,7 @@ struct output_config *new_output_config(const char *name) {
47 oc->scale = -1; 47 oc->scale = -1;
48 oc->transform = -1; 48 oc->transform = -1;
49 oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; 49 oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
50 oc->max_render_time = -1;
50 return oc; 51 return oc;
51} 52}
52 53
@@ -81,6 +82,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
81 if (src->transform != -1) { 82 if (src->transform != -1) {
82 dst->transform = src->transform; 83 dst->transform = src->transform;
83 } 84 }
85 if (src->max_render_time != -1) {
86 dst->max_render_time = src->max_render_time;
87 }
84 if (src->background) { 88 if (src->background) {
85 free(dst->background); 89 free(dst->background);
86 dst->background = strdup(src->background); 90 dst->background = strdup(src->background);
@@ -153,11 +157,12 @@ static void merge_id_on_name(struct output_config *oc) {
153 list_add(config->output_configs, ion_oc); 157 list_add(config->output_configs, ion_oc);
154 sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" 158 sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\""
155 " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " 159 " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f "
156 "transform %d) (bg %s %s) (dpms %d)", ion_oc->name, 160 "transform %d) (bg %s %s) (dpms %d) (max render time: %d)",
157 ion_oc->enabled, ion_oc->width, ion_oc->height, 161 ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height,
158 ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, 162 ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale,
159 ion_oc->transform, ion_oc->background, 163 ion_oc->transform, ion_oc->background,
160 ion_oc->background_option, ion_oc->dpms_state); 164 ion_oc->background_option, ion_oc->dpms_state,
165 ion_oc->max_render_time);
161 } 166 }
162 } 167 }
163 free(id_on_name); 168 free(id_on_name);
@@ -197,10 +202,12 @@ struct output_config *store_output_config(struct output_config *oc) {
197 } 202 }
198 203
199 sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " 204 sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
200 "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d)", 205 "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d) "
206 "(max render time: %d)",
201 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, 207 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate,
202 oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), 208 oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel),
203 oc->transform, oc->background, oc->background_option, oc->dpms_state); 209 oc->transform, oc->background, oc->background_option, oc->dpms_state,
210 oc->max_render_time);
204 211
205 return oc; 212 return oc;
206} 213}
@@ -325,6 +332,12 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
325 wlr_output_enable(wlr_output, false); 332 wlr_output_enable(wlr_output, false);
326 } 333 }
327 334
335 if (oc && oc->max_render_time >= 0) {
336 sway_log(SWAY_DEBUG, "Set %s max render time to %d",
337 oc->name, oc->max_render_time);
338 output->max_render_time = oc->max_render_time;
339 }
340
328 return true; 341 return true;
329} 342}
330 343
@@ -343,6 +356,7 @@ static void default_output_config(struct output_config *oc,
343 oc->subpixel = output->detected_subpixel; 356 oc->subpixel = output->detected_subpixel;
344 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; 357 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
345 oc->dpms_state = DPMS_ON; 358 oc->dpms_state = DPMS_ON;
359 oc->max_render_time = 0;
346} 360}
347 361
348static struct output_config *get_output_config(char *identifier, 362static struct output_config *get_output_config(char *identifier,
@@ -396,10 +410,11 @@ static struct output_config *get_output_config(char *identifier,
396 410
397 sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" 411 sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)"
398 " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" 412 " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)"
399 " (dpms %d)", result->name, result->enabled, result->width, 413 " (dpms %d) (max render time: %d)", result->name, result->enabled,
400 result->height, result->refresh_rate, result->x, result->y, 414 result->width, result->height, result->refresh_rate,
401 result->scale, result->transform, result->background, 415 result->x, result->y, result->scale, result->transform,
402 result->background_option, result->dpms_state); 416 result->background, result->background_option, result->dpms_state,
417 result->max_render_time);
403 } else if (oc_name) { 418 } else if (oc_name) {
404 // No identifier config, just return a copy of the name config 419 // No identifier config, just return a copy of the name config
405 free(result->name); 420 free(result->name);
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 05b5cd44..0f715c19 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -480,19 +480,13 @@ static bool scan_out_fullscreen_view(struct sway_output *output,
480 return wlr_output_commit(wlr_output); 480 return wlr_output_commit(wlr_output);
481} 481}
482 482
483static void damage_handle_frame(struct wl_listener *listener, void *data) { 483int output_repaint_timer_handler(void *data) {
484 struct sway_output *output = 484 struct sway_output *output = data;
485 wl_container_of(listener, output, damage_frame); 485 output->wlr_output->block_idle_frame = false;
486 if (!output->enabled || !output->wlr_output->enabled) {
487 return;
488 }
489
490 struct timespec now;
491 clock_gettime(CLOCK_MONOTONIC, &now);
492 486
493 struct sway_workspace *workspace = output->current.active_workspace; 487 struct sway_workspace *workspace = output->current.active_workspace;
494 if (workspace == NULL) { 488 if (workspace == NULL) {
495 return; 489 return 0;
496 } 490 }
497 491
498 struct sway_container *fullscreen_con = root->fullscreen_global; 492 struct sway_container *fullscreen_con = root->fullscreen_global;
@@ -515,7 +509,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) {
515 last_scanned_out = scanned_out; 509 last_scanned_out = scanned_out;
516 510
517 if (scanned_out) { 511 if (scanned_out) {
518 goto frame_done; 512 return 0;
519 } 513 }
520 } 514 }
521 515
@@ -524,17 +518,82 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) {
524 pixman_region32_init(&damage); 518 pixman_region32_init(&damage);
525 if (!wlr_output_damage_attach_render(output->damage, 519 if (!wlr_output_damage_attach_render(output->damage,
526 &needs_frame, &damage)) { 520 &needs_frame, &damage)) {
527 return; 521 return 0;
528 } 522 }
529 523
530 if (needs_frame) { 524 if (needs_frame) {
525 struct timespec now;
526 clock_gettime(CLOCK_MONOTONIC, &now);
527
531 output_render(output, &now, &damage); 528 output_render(output, &now, &damage);
532 } 529 }
533 530
534 pixman_region32_fini(&damage); 531 pixman_region32_fini(&damage);
535 532
536frame_done: 533 return 0;
534}
535
536static void damage_handle_frame(struct wl_listener *listener, void *data) {
537 struct sway_output *output =
538 wl_container_of(listener, output, damage_frame);
539 if (!output->enabled || !output->wlr_output->enabled) {
540 return;
541 }
542
543 // Compute predicted milliseconds until the next refresh. It's used for
544 // delaying both output rendering and surface frame callbacks.
545 int msec_until_refresh = 0;
546
547 if (output->max_render_time != 0) {
548 struct timespec now;
549 clockid_t presentation_clock
550 = wlr_backend_get_presentation_clock(server.backend);
551 clock_gettime(presentation_clock, &now);
552
553 const long NSEC_IN_SECONDS = 1000000000;
554 struct timespec predicted_refresh = output->last_presentation;
555 predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS;
556 predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS;
557 if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) {
558 predicted_refresh.tv_sec += 1;
559 predicted_refresh.tv_nsec -= NSEC_IN_SECONDS;
560 }
561
562 // If the predicted refresh time is before the current time then
563 // there's no point in delaying.
564 //
565 // We only check tv_sec because if the predicted refresh time is less
566 // than a second before the current time, then msec_until_refresh will
567 // end up slightly below zero, which will effectively disable the delay
568 // without potential disasterous negative overflows that could occur if
569 // tv_sec was not checked.
570 if (predicted_refresh.tv_sec >= now.tv_sec) {
571 long nsec_until_refresh
572 = (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS
573 + (predicted_refresh.tv_nsec - now.tv_nsec);
574
575 // We want msec_until_refresh to be conservative, that is, floored.
576 // If we have 7.9 msec until refresh, we better compute the delay
577 // as if we had only 7 msec, so that we don't accidentally delay
578 // more than necessary and miss a frame.
579 msec_until_refresh = nsec_until_refresh / 1000000;
580 }
581 }
582
583 int delay = msec_until_refresh - output->max_render_time;
584
585 // If the delay is less than 1 millisecond (which is the least we can wait)
586 // then just render right away.
587 if (delay < 1) {
588 output_repaint_timer_handler(output);
589 } else {
590 output->wlr_output->block_idle_frame = true;
591 wl_event_source_timer_update(output->repaint_timer, delay);
592 }
593
537 // Send frame done to all visible surfaces 594 // Send frame done to all visible surfaces
595 struct timespec now;
596 clock_gettime(CLOCK_MONOTONIC, &now);
538 send_frame_done(output, &now); 597 send_frame_done(output, &now);
539} 598}
540 599
@@ -768,6 +827,9 @@ static void handle_present(struct wl_listener *listener, void *data) {
768 return; 827 return;
769 } 828 }
770 829
830 output->last_presentation = *output_event->when;
831 output->refresh_nsec = output_event->refresh;
832
771 struct wlr_presentation_event event = { 833 struct wlr_presentation_event event = {
772 .output = output->wlr_output, 834 .output = output->wlr_output,
773 .tv_sec = (uint64_t)output_event->when->tv_sec, 835 .tv_sec = (uint64_t)output_event->when->tv_sec,
@@ -806,6 +868,9 @@ void handle_new_output(struct wl_listener *listener, void *data) {
806 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); 868 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
807 output->damage_destroy.notify = damage_handle_destroy; 869 output->damage_destroy.notify = damage_handle_destroy;
808 870
871 output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
872 output_repaint_timer_handler, output);
873
809 struct output_config *oc = find_output_config(output); 874 struct output_config *oc = find_output_config(output);
810 if (!oc || oc->enabled) { 875 if (!oc || oc->enabled) {
811 output_enable(output, oc); 876 output_enable(output, oc);
diff --git a/sway/meson.build b/sway/meson.build
index 8fcf841c..76a31d73 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -175,6 +175,7 @@ sway_sources = files(
175 'commands/output/disable.c', 175 'commands/output/disable.c',
176 'commands/output/dpms.c', 176 'commands/output/dpms.c',
177 'commands/output/enable.c', 177 'commands/output/enable.c',
178 'commands/output/max_render_time.c',
178 'commands/output/mode.c', 179 'commands/output/mode.c',
179 'commands/output/position.c', 180 'commands/output/position.c',
180 'commands/output/scale.c', 181 'commands/output/scale.c',
diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd
index ae6ced24..01496f19 100644
--- a/sway/sway-output.5.scd
+++ b/sway/sway-output.5.scd
@@ -107,6 +107,30 @@ must be separated by one space. For example:
107 Enables or disables the specified output via DPMS. To turn an output off 107 Enables or disables the specified output via DPMS. To turn an output off
108 (ie. blank the screen but keep workspaces as-is), one can set DPMS to off. 108 (ie. blank the screen but keep workspaces as-is), one can set DPMS to off.
109 109
110*output* <name> max_render_time off|<msec>
111 When set to a positive number of milliseconds, enables delaying output
112 rendering to reduce latency. The rendering is delayed in such a way as
113 to leave the specified number of milliseconds before the next
114 presentation for rendering.
115
116 The output rendering normally takes place immediately after a
117 presentation (vblank, buffer flip, etc.) and the frame callbacks are
118 sent to surfaces immediately after the rendering to give surfaces the
119 most time to draw their next frame. This results in slightly below 2
120 frames of latency between the surface rendering and committing new
121 contents, and the contents being shown on screen, on average. When the
122 output rendering is delayed, the frame callbacks are sent immediately
123 after presentation, and the surfaces have a small timespan (1 /
124 (refresh rate) - max_render_time) to render and commit new contents to
125 be shown on the next presentation, resulting in below 1 frame of
126 latency.
127
128 To set this up for optimal latency:
129 . Launch some _full-screen_ application that renders continuously, like
130 *glxgears*.
131 . Start with *max_render_time 1*. Increment by *1* if you see frame
132 drops.
133
110# SEE ALSO 134# SEE ALSO
111 135
112*sway*(5) *sway-input*(5) 136*sway*(5) *sway-input*(5)
diff --git a/sway/tree/output.c b/sway/tree/output.c
index 07f04ee5..c4ec6eec 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -242,6 +242,7 @@ void output_destroy(struct sway_output *output) {
242 } 242 }
243 list_free(output->workspaces); 243 list_free(output->workspaces);
244 list_free(output->current.workspaces); 244 list_free(output->current.workspaces);
245 wl_event_source_remove(output->repaint_timer);
245 free(output); 246 free(output);
246} 247}
247 248