output: add max_render_time

This commit is contained in:
Ivan Molodetskikh 2019-09-25 13:58:27 +03:00 committed by Simon Ser
parent cb905effde
commit 022df2542b
10 changed files with 166 additions and 22 deletions

View file

@ -271,6 +271,7 @@ sway_cmd output_cmd_background;
sway_cmd output_cmd_disable; sway_cmd output_cmd_disable;
sway_cmd output_cmd_dpms; sway_cmd output_cmd_dpms;
sway_cmd output_cmd_enable; sway_cmd output_cmd_enable;
sway_cmd output_cmd_max_render_time;
sway_cmd output_cmd_mode; sway_cmd output_cmd_mode;
sway_cmd output_cmd_position; sway_cmd output_cmd_position;
sway_cmd output_cmd_scale; sway_cmd output_cmd_scale;

View file

@ -212,6 +212,7 @@ struct output_config {
float scale; float scale;
int32_t transform; int32_t transform;
enum wl_output_subpixel subpixel; enum wl_output_subpixel subpixel;
int max_render_time; // In milliseconds
char *background; char *background;
char *background_option; char *background_option;

View file

@ -51,6 +51,11 @@ struct sway_output {
struct { struct {
struct wl_signal destroy; struct wl_signal destroy;
} events; } events;
struct timespec last_presentation;
uint32_t refresh_nsec;
int max_render_time; // In milliseconds
struct wl_event_source *repaint_timer;
}; };
struct sway_output *output_create(struct wlr_output *wlr_output); struct sway_output *output_create(struct wlr_output *wlr_output);
@ -71,6 +76,8 @@ typedef void (*sway_surface_iterator_func_t)(struct sway_output *output,
struct wlr_surface *surface, struct wlr_box *box, float rotation, struct wlr_surface *surface, struct wlr_box *box, float rotation,
void *user_data); void *user_data);
int output_repaint_timer_handler(void *data);
void output_damage_whole(struct sway_output *output); void output_damage_whole(struct sway_output *output);
void output_damage_surface(struct sway_output *output, double ox, double oy, void output_damage_surface(struct sway_output *output, double ox, double oy,

View file

@ -12,6 +12,7 @@ static struct cmd_handler output_handlers[] = {
{ "disable", output_cmd_disable }, { "disable", output_cmd_disable },
{ "dpms", output_cmd_dpms }, { "dpms", output_cmd_dpms },
{ "enable", output_cmd_enable }, { "enable", output_cmd_enable },
{ "max_render_time", output_cmd_max_render_time },
{ "mode", output_cmd_mode }, { "mode", output_cmd_mode },
{ "pos", output_cmd_position }, { "pos", output_cmd_position },
{ "position", output_cmd_position }, { "position", output_cmd_position },

View file

@ -0,0 +1,28 @@
#include <strings.h>
#include "sway/commands.h"
#include "sway/config.h"
struct cmd_results *output_cmd_max_render_time(int argc, char **argv) {
if (!config->handler_context.output_config) {
return cmd_results_new(CMD_FAILURE, "Missing output config");
}
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.");
}
}
config->handler_context.output_config->max_render_time = max_render_time;
config->handler_context.leftovers.argc = argc - 1;
config->handler_context.leftovers.argv = argv + 1;
return NULL;
}

View file

@ -47,6 +47,7 @@ struct output_config *new_output_config(const char *name) {
oc->scale = -1; oc->scale = -1;
oc->transform = -1; oc->transform = -1;
oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
oc->max_render_time = -1;
return oc; return oc;
} }
@ -81,6 +82,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
if (src->transform != -1) { if (src->transform != -1) {
dst->transform = src->transform; dst->transform = src->transform;
} }
if (src->max_render_time != -1) {
dst->max_render_time = src->max_render_time;
}
if (src->background) { if (src->background) {
free(dst->background); free(dst->background);
dst->background = strdup(src->background); dst->background = strdup(src->background);
@ -153,11 +157,12 @@ static void merge_id_on_name(struct output_config *oc) {
list_add(config->output_configs, ion_oc); list_add(config->output_configs, ion_oc);
sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\""
" (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f "
"transform %d) (bg %s %s) (dpms %d)", ion_oc->name, "transform %d) (bg %s %s) (dpms %d) (max render time: %d)",
ion_oc->enabled, ion_oc->width, ion_oc->height, ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height,
ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale,
ion_oc->transform, ion_oc->background, ion_oc->transform, ion_oc->background,
ion_oc->background_option, ion_oc->dpms_state); ion_oc->background_option, ion_oc->dpms_state,
ion_oc->max_render_time);
} }
} }
free(id_on_name); free(id_on_name);
@ -197,10 +202,12 @@ struct output_config *store_output_config(struct output_config *oc) {
} }
sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
"position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d)", "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d) "
"(max render time: %d)",
oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate,
oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel),
oc->transform, oc->background, oc->background_option, oc->dpms_state); oc->transform, oc->background, oc->background_option, oc->dpms_state,
oc->max_render_time);
return oc; return oc;
} }
@ -325,6 +332,12 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
wlr_output_enable(wlr_output, false); wlr_output_enable(wlr_output, false);
} }
if (oc && oc->max_render_time >= 0) {
sway_log(SWAY_DEBUG, "Set %s max render time to %d",
oc->name, oc->max_render_time);
output->max_render_time = oc->max_render_time;
}
return true; return true;
} }
@ -343,6 +356,7 @@ static void default_output_config(struct output_config *oc,
oc->subpixel = output->detected_subpixel; oc->subpixel = output->detected_subpixel;
oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
oc->dpms_state = DPMS_ON; oc->dpms_state = DPMS_ON;
oc->max_render_time = 0;
} }
static struct output_config *get_output_config(char *identifier, static struct output_config *get_output_config(char *identifier,
@ -396,10 +410,11 @@ static struct output_config *get_output_config(char *identifier,
sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)"
" (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)"
" (dpms %d)", result->name, result->enabled, result->width, " (dpms %d) (max render time: %d)", result->name, result->enabled,
result->height, result->refresh_rate, result->x, result->y, result->width, result->height, result->refresh_rate,
result->scale, result->transform, result->background, result->x, result->y, result->scale, result->transform,
result->background_option, result->dpms_state); result->background, result->background_option, result->dpms_state,
result->max_render_time);
} else if (oc_name) { } else if (oc_name) {
// No identifier config, just return a copy of the name config // No identifier config, just return a copy of the name config
free(result->name); free(result->name);

View file

@ -480,19 +480,13 @@ static bool scan_out_fullscreen_view(struct sway_output *output,
return wlr_output_commit(wlr_output); return wlr_output_commit(wlr_output);
} }
static void damage_handle_frame(struct wl_listener *listener, void *data) { int output_repaint_timer_handler(void *data) {
struct sway_output *output = struct sway_output *output = data;
wl_container_of(listener, output, damage_frame); output->wlr_output->block_idle_frame = false;
if (!output->enabled || !output->wlr_output->enabled) {
return;
}
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
struct sway_workspace *workspace = output->current.active_workspace; struct sway_workspace *workspace = output->current.active_workspace;
if (workspace == NULL) { if (workspace == NULL) {
return; return 0;
} }
struct sway_container *fullscreen_con = root->fullscreen_global; struct sway_container *fullscreen_con = root->fullscreen_global;
@ -515,7 +509,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) {
last_scanned_out = scanned_out; last_scanned_out = scanned_out;
if (scanned_out) { if (scanned_out) {
goto frame_done; return 0;
} }
} }
@ -524,17 +518,82 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) {
pixman_region32_init(&damage); pixman_region32_init(&damage);
if (!wlr_output_damage_attach_render(output->damage, if (!wlr_output_damage_attach_render(output->damage,
&needs_frame, &damage)) { &needs_frame, &damage)) {
return; return 0;
} }
if (needs_frame) { if (needs_frame) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
output_render(output, &now, &damage); output_render(output, &now, &damage);
} }
pixman_region32_fini(&damage); pixman_region32_fini(&damage);
frame_done: return 0;
}
static void damage_handle_frame(struct wl_listener *listener, void *data) {
struct sway_output *output =
wl_container_of(listener, output, damage_frame);
if (!output->enabled || !output->wlr_output->enabled) {
return;
}
// Compute predicted milliseconds until the next refresh. It's used for
// delaying both output rendering and surface frame callbacks.
int msec_until_refresh = 0;
if (output->max_render_time != 0) {
struct timespec now;
clockid_t presentation_clock
= wlr_backend_get_presentation_clock(server.backend);
clock_gettime(presentation_clock, &now);
const long NSEC_IN_SECONDS = 1000000000;
struct timespec predicted_refresh = output->last_presentation;
predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS;
predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS;
if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) {
predicted_refresh.tv_sec += 1;
predicted_refresh.tv_nsec -= NSEC_IN_SECONDS;
}
// If the predicted refresh time is before the current time then
// there's no point in delaying.
//
// We only check tv_sec because if the predicted refresh time is less
// than a second before the current time, then msec_until_refresh will
// end up slightly below zero, which will effectively disable the delay
// without potential disasterous negative overflows that could occur if
// tv_sec was not checked.
if (predicted_refresh.tv_sec >= now.tv_sec) {
long nsec_until_refresh
= (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS
+ (predicted_refresh.tv_nsec - now.tv_nsec);
// We want msec_until_refresh to be conservative, that is, floored.
// If we have 7.9 msec until refresh, we better compute the delay
// as if we had only 7 msec, so that we don't accidentally delay
// more than necessary and miss a frame.
msec_until_refresh = nsec_until_refresh / 1000000;
}
}
int delay = msec_until_refresh - output->max_render_time;
// If the delay is less than 1 millisecond (which is the least we can wait)
// then just render right away.
if (delay < 1) {
output_repaint_timer_handler(output);
} else {
output->wlr_output->block_idle_frame = true;
wl_event_source_timer_update(output->repaint_timer, delay);
}
// Send frame done to all visible surfaces // Send frame done to all visible surfaces
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
send_frame_done(output, &now); send_frame_done(output, &now);
} }
@ -768,6 +827,9 @@ static void handle_present(struct wl_listener *listener, void *data) {
return; return;
} }
output->last_presentation = *output_event->when;
output->refresh_nsec = output_event->refresh;
struct wlr_presentation_event event = { struct wlr_presentation_event event = {
.output = output->wlr_output, .output = output->wlr_output,
.tv_sec = (uint64_t)output_event->when->tv_sec, .tv_sec = (uint64_t)output_event->when->tv_sec,
@ -806,6 +868,9 @@ void handle_new_output(struct wl_listener *listener, void *data) {
wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
output->damage_destroy.notify = damage_handle_destroy; output->damage_destroy.notify = damage_handle_destroy;
output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
output_repaint_timer_handler, output);
struct output_config *oc = find_output_config(output); struct output_config *oc = find_output_config(output);
if (!oc || oc->enabled) { if (!oc || oc->enabled) {
output_enable(output, oc); output_enable(output, oc);

View file

@ -175,6 +175,7 @@ sway_sources = files(
'commands/output/disable.c', 'commands/output/disable.c',
'commands/output/dpms.c', 'commands/output/dpms.c',
'commands/output/enable.c', 'commands/output/enable.c',
'commands/output/max_render_time.c',
'commands/output/mode.c', 'commands/output/mode.c',
'commands/output/position.c', 'commands/output/position.c',
'commands/output/scale.c', 'commands/output/scale.c',

View file

@ -107,6 +107,30 @@ must be separated by one space. For example:
Enables or disables the specified output via DPMS. To turn an output off Enables or disables the specified output via DPMS. To turn an output off
(ie. blank the screen but keep workspaces as-is), one can set DPMS to off. (ie. blank the screen but keep workspaces as-is), one can set DPMS to off.
*output* <name> max_render_time off|<msec>
When set to a positive number of milliseconds, enables delaying output
rendering to reduce latency. The rendering is delayed in such a way as
to leave the specified number of milliseconds before the next
presentation for rendering.
The output rendering normally takes place immediately after a
presentation (vblank, buffer flip, etc.) and the frame callbacks are
sent to surfaces immediately after the rendering to give surfaces the
most time to draw their next frame. This results in slightly below 2
frames of latency between the surface rendering and committing new
contents, and the contents being shown on screen, on average. When the
output rendering is delayed, the frame callbacks are sent immediately
after presentation, and the surfaces have a small timespan (1 /
(refresh rate) - max_render_time) to render and commit new contents to
be shown on the next presentation, resulting in below 1 frame of
latency.
To set this up for optimal latency:
. Launch some _full-screen_ application that renders continuously, like
*glxgears*.
. Start with *max_render_time 1*. Increment by *1* if you see frame
drops.
# SEE ALSO # SEE ALSO
*sway*(5) *sway-input*(5) *sway*(5) *sway-input*(5)

View file

@ -242,6 +242,7 @@ void output_destroy(struct sway_output *output) {
} }
list_free(output->workspaces); list_free(output->workspaces);
list_free(output->current.workspaces); list_free(output->current.workspaces);
wl_event_source_remove(output->repaint_timer);
free(output); free(output);
} }