output: add max_render_time
This commit is contained in:
parent
cb905effde
commit
022df2542b
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
28
sway/commands/output/max_render_time.c
Normal file
28
sway/commands/output/max_render_time.c
Normal 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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue