diff --git a/include/sway/commands.h b/include/sway/commands.h index 67665d87..5f249980 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -276,6 +276,7 @@ sway_cmd output_cmd_max_render_time; sway_cmd output_cmd_mode; sway_cmd output_cmd_position; sway_cmd output_cmd_scale; +sway_cmd output_cmd_scale_filter; sway_cmd output_cmd_subpixel; sway_cmd output_cmd_toggle; sway_cmd output_cmd_transform; diff --git a/include/sway/config.h b/include/sway/config.h index ed542790..9a00ccb5 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -204,6 +204,13 @@ enum config_dpms { DPMS_OFF }; +enum scale_filter_mode { + SCALE_FILTER_DEFAULT, // the default is currently smart + SCALE_FILTER_LINEAR, + SCALE_FILTER_NEAREST, + SCALE_FILTER_SMART +}; + /** * Size and position configuration for a particular output. * @@ -217,6 +224,7 @@ struct output_config { int custom_mode; int x, y; float scale; + enum scale_filter_mode scale_filter; int32_t transform; enum wl_output_subpixel subpixel; int max_render_time; // In milliseconds @@ -655,6 +663,8 @@ int output_name_cmp(const void *item, const void *data); void output_get_identifier(char *identifier, size_t len, struct sway_output *output); +const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter); + struct output_config *new_output_config(const char *name); void merge_output_config(struct output_config *dst, struct output_config *src); diff --git a/include/sway/output.h b/include/sway/output.h index ddc08022..bc03f4c5 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -32,6 +32,7 @@ struct sway_output { int lx, ly; // layout coords int width, height; // transformed buffer size enum wl_output_subpixel detected_subpixel; + enum scale_filter_mode scale_filter; // last applied mode when the output is DPMS'ed struct wlr_output_mode *current_mode; diff --git a/meson.build b/meson.build index 160c921f..70afcd50 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,7 @@ pango = dependency('pango') pangocairo = dependency('pangocairo') gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) pixman = dependency('pixman-1') +glesv2 = dependency('glesv2') libevdev = dependency('libevdev') libinput = dependency('libinput', version: '>=1.6.0') systemd = dependency('libsystemd', version: '>=239', required: false) diff --git a/sway/commands/output.c b/sway/commands/output.c index db2acb50..2790bd63 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c @@ -19,6 +19,7 @@ static struct cmd_handler output_handlers[] = { { "res", output_cmd_mode }, { "resolution", output_cmd_mode }, { "scale", output_cmd_scale }, + { "scale_filter", output_cmd_scale_filter }, { "subpixel", output_cmd_subpixel }, { "toggle", output_cmd_toggle }, { "transform", output_cmd_transform }, diff --git a/sway/commands/output/scale_filter.c b/sway/commands/output/scale_filter.c new file mode 100644 index 00000000..fa1e8e0d --- /dev/null +++ b/sway/commands/output/scale_filter.c @@ -0,0 +1,34 @@ +#include +#include "log.h" +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" + +struct cmd_results *output_cmd_scale_filter(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 scale_filter argument."); + } + + + enum scale_filter_mode scale_filter; + if (strcmp(*argv, "linear") == 0) { + scale_filter = SCALE_FILTER_LINEAR; + } else if (strcmp(*argv, "nearest") == 0) { + scale_filter = SCALE_FILTER_NEAREST; + } else if (strcmp(*argv, "smart") == 0) { + scale_filter = SCALE_FILTER_SMART; + } else { + return cmd_results_new(CMD_INVALID, "Invalid output scale_filter."); + } + + struct output_config *oc = config->handler_context.output_config; + config->handler_context.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + + oc->scale_filter = scale_filter; + return NULL; +} diff --git a/sway/config/output.c b/sway/config/output.c index 1d5f81da..21a12b8f 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -29,6 +29,21 @@ void output_get_identifier(char *identifier, size_t len, wlr_output->serial); } +const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) { + switch (scale_filter) { + case SCALE_FILTER_DEFAULT: + return "smart"; + case SCALE_FILTER_LINEAR: + return "linear"; + case SCALE_FILTER_NEAREST: + return "nearest"; + case SCALE_FILTER_SMART: + return "smart"; + } + sway_assert(false, "Unknown value for scale_filter."); + return NULL; +} + struct output_config *new_output_config(const char *name) { struct output_config *oc = calloc(1, sizeof(struct output_config)); if (oc == NULL) { @@ -45,6 +60,7 @@ struct output_config *new_output_config(const char *name) { oc->custom_mode = -1; oc->x = oc->y = -1; oc->scale = -1; + oc->scale_filter = SCALE_FILTER_DEFAULT; oc->transform = -1; oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; oc->max_render_time = -1; @@ -70,6 +86,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->scale != -1) { dst->scale = src->scale; } + if (src->scale_filter != SCALE_FILTER_DEFAULT) { + dst->scale_filter = src->scale_filter; + } if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { dst->subpixel = src->subpixel; } @@ -297,6 +316,24 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { if (oc && oc->scale > 0) { sway_log(SWAY_DEBUG, "Set %s scale to %f", oc->name, oc->scale); wlr_output_set_scale(wlr_output, oc->scale); + + enum scale_filter_mode scale_filter_old = output->scale_filter; + switch (oc->scale_filter) { + case SCALE_FILTER_DEFAULT: + case SCALE_FILTER_SMART: + output->scale_filter = ceilf(wlr_output->scale) == wlr_output->scale ? + SCALE_FILTER_NEAREST : SCALE_FILTER_LINEAR; + break; + case SCALE_FILTER_LINEAR: + case SCALE_FILTER_NEAREST: + output->scale_filter = oc->scale_filter; + break; + } + if (scale_filter_old != output->scale_filter) { + sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, + sway_output_scale_filter_to_string(output->scale_filter)); + output_damage_whole(output); + } } if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { @@ -352,6 +389,7 @@ static void default_output_config(struct output_config *oc, } oc->x = oc->y = -1; oc->scale = 1; + oc->scale_filter = SCALE_FILTER_DEFAULT; struct sway_output *output = wlr_output->data; oc->subpixel = output->detected_subpixel; oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 960fe083..2e66abd4 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -1,9 +1,11 @@ #define _POSIX_C_SOURCE 200809L #include +#include #include #include #include #include +#include #include #include #include @@ -70,6 +72,28 @@ static void scissor_output(struct wlr_output *wlr_output, wlr_renderer_scissor(renderer, &box); } +static void set_scale_filter(struct wlr_output *wlr_output, + struct wlr_texture *texture, enum scale_filter_mode scale_filter) { + if (!wlr_texture_is_gles2(texture)) { + return; + } + + struct wlr_gles2_texture_attribs attribs; + wlr_gles2_texture_get_attribs(texture, &attribs); + + switch (scale_filter) { + case SCALE_FILTER_LINEAR: + glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + case SCALE_FILTER_NEAREST: + glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + case SCALE_FILTER_DEFAULT: + case SCALE_FILTER_SMART: + assert(false); // unreachable + } +} + static void render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture, const struct wlr_box *box, const float matrix[static 9], float alpha) { @@ -119,6 +143,7 @@ static void render_surface_iterator(struct sway_output *output, struct sway_view wlr_matrix_project_box(matrix, &box, transform, rotation, wlr_output->transform_matrix); + set_scale_filter(wlr_output, texture, output->scale_filter); render_texture(wlr_output, output_damage, texture, &box, matrix, alpha); wlr_presentation_surface_sampled_on_output(server.presentation, surface, @@ -268,6 +293,7 @@ static void render_saved_view(struct sway_view *view, wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, wlr_output->transform_matrix); + set_scale_filter(wlr_output, view->saved_buffer->texture, output->scale_filter); render_texture(wlr_output, damage, view->saved_buffer->texture, &box, matrix, alpha); diff --git a/sway/ipc-json.c b/sway/ipc-json.c index b880eb65..6cf8504a 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -177,6 +177,9 @@ static void ipc_json_describe_output(struct sway_output *output, json_object_new_string(wlr_output->serial)); json_object_object_add(object, "scale", json_object_new_double(wlr_output->scale)); + json_object_object_add(object, "scale_filter", + json_object_new_string( + sway_output_scale_filter_to_string(output->scale_filter))); json_object_object_add(object, "transform", json_object_new_string( ipc_json_output_transform_description(wlr_output->transform))); diff --git a/sway/meson.build b/sway/meson.build index 5458d3dc..3e6e4da6 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -182,6 +182,7 @@ sway_sources = files( 'commands/output/mode.c', 'commands/output/position.c', 'commands/output/scale.c', + 'commands/output/scale_filter.c', 'commands/output/subpixel.c', 'commands/output/toggle.c', 'commands/output/transform.c', @@ -203,6 +204,7 @@ sway_deps = [ math, pango, pcre, + glesv2, pixman, server_protos, wayland_server, diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index 3824480f..2dfb2ac1 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -70,6 +70,14 @@ must be separated by one space. For example: applications to taste. HiDPI isn't supported with Xwayland clients (windows will blur). +*output* scale_filter linear|nearest|smart + Indicates how to scale application buffers that are rendered at a scale + lower than the output's configured scale, such as lo-dpi applications on + hi-dpi screens. Linear is smoother and blurrier, nearest (also known as + nearest neighbor) is sharper and blockier. Setting "smart" will apply + nearest scaling when the output has an integer scale factor, otherwise + linear. The default is "smart". + *output* subpixel rgb|bgr|vrgb|vbgr|none Manually sets the subpixel hinting for the specified output. This value is usually auto-detected, but some displays may misreport their subpixel diff --git a/sway/tree/output.c b/sway/tree/output.c index c4ec6eec..d2ede1f2 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -93,6 +93,7 @@ struct sway_output *output_create(struct wlr_output *wlr_output) { output->wlr_output = wlr_output; wlr_output->data = output; output->detected_subpixel = wlr_output->subpixel; + output->scale_filter = SCALE_FILTER_NEAREST; wl_signal_init(&output->events.destroy); diff --git a/swaymsg/main.c b/swaymsg/main.c index 596cf4ef..b9a8189c 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -189,11 +189,13 @@ static void pretty_print_output(json_object *o) { json_object_object_get_ex(o, "focused", &focused); json_object_object_get_ex(o, "active", &active); json_object_object_get_ex(o, "current_workspace", &ws); - json_object *make, *model, *serial, *scale, *subpixel, *transform, *max_render_time; + json_object *make, *model, *serial, *scale, *scale_filter, *subpixel, + *transform, *max_render_time; json_object_object_get_ex(o, "make", &make); json_object_object_get_ex(o, "model", &model); json_object_object_get_ex(o, "serial", &serial); json_object_object_get_ex(o, "scale", &scale); + json_object_object_get_ex(o, "scale_filter", &scale_filter); json_object_object_get_ex(o, "subpixel_hinting", &subpixel); json_object_object_get_ex(o, "transform", &transform); json_object_object_get_ex(o, "max_render_time", &max_render_time); @@ -214,6 +216,7 @@ static void pretty_print_output(json_object *o) { " Current mode: %dx%d @ %f Hz\n" " Position: %d,%d\n" " Scale factor: %f\n" + " Scale filter: %s\n" " Subpixel hinting: %s\n" " Transform: %s\n" " Workspace: %s\n" @@ -228,6 +231,7 @@ static void pretty_print_output(json_object *o) { (float)json_object_get_int(refresh) / 1000, json_object_get_int(x), json_object_get_int(y), json_object_get_double(scale), + json_object_get_string(scale_filter), json_object_get_string(subpixel), json_object_get_string(transform), json_object_get_string(ws)