config: tweak.surface-bit-depth now defaults to 'auto'

When set to 'auto', use 10-bit surfaces if gamma-correct blending is
enabled, and 8-bit surfaces otherwise.

Note that we may still fallback to 8-bit surfaces (without disabling
gamma-correct blending) if the compositor does not support 10-bit
surfaces.

Closes #2082
This commit is contained in:
Daniel Eklöf 2025-05-01 08:34:49 +02:00
parent b07ce56321
commit e5a0755451
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
13 changed files with 105 additions and 71 deletions

View file

@ -84,6 +84,7 @@
- paper-color
- selenized
- solarized
* `auto` to the `tweak.surface-bit-depth` option.
[2025]: https://codeberg.org/dnkl/foot/issues/2025
@ -92,6 +93,9 @@
* `cursor.color` moved to `colors.cursor`.
* `gamma-correct-blending` now defaults to `no` instead of `yes`.
* `tweak.surface-bit-depth` default value changed to `auto`; uses
10-bit surfaces when `gamma-correct-blending=yes`, and 8-bit
surfaces otherwise.
### Deprecated
@ -101,6 +105,12 @@
### Removed
### Fixed
* Inaccurate colors when `gamma-correct-blending=yes` ([#2082][2082]).
[2082]: https://codeberg.org/dnkl/foot/issues/2082
### Security
### Contributors

View file

@ -2813,7 +2813,7 @@ parse_section_tweak(struct context *ctx)
return value_to_enum(
ctx,
(const char *[]){"8-bit", "10-bit", NULL},
(const char *[]){"auto", "8-bit", "10-bit", NULL},
(int *)&conf->tweak.surface_bit_depth);
}
@ -3463,7 +3463,7 @@ config_load(struct config *conf, const char *conf_path,
.box_drawing_solid_shades = true,
.font_monospace_warn = true,
.sixel = true,
.surface_bit_depth = 8,
.surface_bit_depth = SHM_BITS_AUTO,
},
.touch = {

View file

@ -195,6 +195,12 @@ enum which_color_theme {
COLOR_THEME2,
};
enum shm_bit_depth {
SHM_BITS_AUTO,
SHM_BITS_8,
SHM_BITS_10
};
struct config {
char *term;
char *shell;
@ -419,7 +425,7 @@ struct config {
bool box_drawing_solid_shades;
bool font_monospace_warn;
bool sixel;
enum { SHM_8_BIT, SHM_10_BIT } surface_bit_depth;
enum shm_bit_depth surface_bit_depth;
} tweak;
struct {

View file

@ -220,12 +220,13 @@ empty string to be set, but it must be quoted: *KEY=""*)
than intended when rendered with gamma-correct blending, since the
font designer set the font weight based on incorrect rendering.
Note that some colors (especially dark ones) may be slightly
off. The reason for this is loss of color precision, due to foot
using 8-bit surfaces (i.e. each color channel is 8 bits). In all
known cases, the difference is small enough not to be noticed
though. The amount of errors can be reduced by using 10-bit
surfaces; see *tweak.surface-bit-depth*.
In order to represent colors faithfully, higher precision image
buffers are required. By default, foot will use 10-bit color
channels, if available, when gamma-correct blending is
enabled. However, the high precision buffers are slow; if you want
to use gamma-correct blending, but prefer speed (throughput and
input latency) over accurate colors, you can force 8-bit color
channels by setting *tweak.surface-bit-depth=8-bit*.
Default: _no_.
@ -2019,23 +2020,25 @@ any of these options.
*surface-bit-depth*
Selects which RGB bit depth to use for image buffers. One of
*8-bit*, or *10-bit*.
*auto*, *8-bit*, or *10-bit*.
The default, *8-bit*, uses 8 bits for all channels, alpha
included. When *gamma-correct-blending* is disabled, this is the
best option.
*auto* chooses bit depth depending on other settings, and
availability.
When *gamma-correct-blending* is enabled, you may want to enable
10-bit surfaces, as that improves color precision. Be aware
however, that in this mode, the alpha channel is only 2 bits
instead of 8 bits. Thus, if you are using a transparent
background, you may want to use the default, *8-bit*, even if you
have gamma-correct blending enabled.
*8-bit*, uses 8 bits for each color channel, alpha included. This
is the default when *gamma-correct-blending=no*.
You should also note that 10-bit surface is much slower. This will
increase input latency and decrease rendering throughput.
*10-bit* uses 10 bits for each RGB channel, and 2 bits for the
alpha channel. Thus, it provides higher precision color channels,
but a lower precision alpha channel. It is the default when
*gamma-correct-blending=yes*, if supported by the compositor.
Default: _8-bit_
Note that *10-bit* is much slower than *8-bit*; if you want to use
gamma-correct blending, and if you prefer speed (throughput and
input latency) over accurate colors, you can set
*surface-bit-depth=8-bit* explicitly.
Default: _auto_
# SEE ALSO

View file

@ -129,7 +129,7 @@ render_worker_thread(void *_ctx)
}
bool
render_do_linear_blending(const struct terminal *term)
wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf)
{
return false;
}
@ -201,11 +201,12 @@ void urls_reset(struct terminal *term) {}
void shm_unref(struct buffer *buf) {}
void shm_chain_free(struct buffer_chain *chain) {}
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { return SHM_BITS_8; }
struct buffer_chain *
shm_chain_new(
struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_it_if_capable)
enum shm_bit_depth desired_bit_depth)
{
return NULL;
}

View file

@ -626,7 +626,7 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
pixman_color_t cursor_color;
pixman_color_t text_color;
cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color,
render_do_linear_blending(term));
wayl_do_linear_blending(term->wl, term->conf));
if (unlikely(!term->kbd_focus)) {
switch (term->conf->cursor.unfocused_style) {
@ -820,7 +820,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
if (cell->attrs.blink && term->blink.state == BLINK_OFF)
_fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount);
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
@ -1180,7 +1180,8 @@ static void
render_urgency(struct terminal *term, struct buffer *buf)
{
uint32_t red = term->colors.table[1];
pixman_color_t bg = color_hex_to_pixman(red, render_do_linear_blending(term));
pixman_color_t bg = color_hex_to_pixman(
red, wayl_do_linear_blending(term->wl, term->conf));
int width = min(min(term->margins.left, term->margins.right),
min(term->margins.top, term->margins.bottom));
@ -1211,7 +1212,7 @@ render_margin(struct terminal *term, struct buffer *buf,
const int bmargin = term->height - term->margins.bottom;
const int line_count = end_line - start_line;
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
uint16_t alpha = term->colors.alpha;
@ -1699,7 +1700,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
if (unlikely(term->is_searching))
return;
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
/* Adjust cursor position to viewport */
struct coord cursor;
@ -1970,7 +1971,8 @@ render_overlay(struct terminal *term)
case OVERLAY_FLASH:
color = color_hex_to_pixman_with_alpha(
term->conf->colors.flash,
term->conf->colors.flash_alpha, render_do_linear_blending(term));
term->conf->colors.flash_alpha,
wayl_do_linear_blending(term->wl, term->conf));
break;
case OVERLAY_NONE:
@ -2312,7 +2314,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf,
pixman_image_set_clip_region32(buf->pix[0], &clip);
pixman_region32_fini(&clip);
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
pixman_image_fill_rectangles(
@ -2453,7 +2455,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx,
if (info->width == 0 || info->height == 0)
return;
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
{
/* Fully transparent - no need to do a color space transform */
@ -2542,7 +2544,7 @@ get_csd_button_fg_color(const struct terminal *term)
}
return color_hex_to_pixman_with_alpha(
_color, alpha, render_do_linear_blending(term));
_color, alpha, wayl_do_linear_blending(term->wl, term->conf));
}
static void
@ -2819,7 +2821,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
if (!term->visual_focus)
_color = color_dim(term, _color);
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct);
render_csd_part(term, surf->surf, buf, info->width, info->height, &color);
@ -3678,7 +3680,7 @@ render_search_box(struct terminal *term)
: term->conf->colors.use_custom.search_box_no_match;
/* Background - yellow on empty/match, red on mismatch (default) */
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
const pixman_color_t color = color_hex_to_pixman(
is_match
? (custom_colors
@ -5247,10 +5249,3 @@ render_xcursor_set(struct seat *seat, struct terminal *term,
seat->pointer.xcursor_pending = true;
return true;
}
bool
render_do_linear_blending(const struct terminal *term)
{
return term->conf->gamma_correct &&
term->wl->color_management.img_description != NULL;
}

View file

@ -47,5 +47,3 @@ struct csd_data {
};
struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx);
bool render_do_linear_blending(const struct terminal *term);

15
shm.c
View file

@ -972,7 +972,7 @@ shm_unref(struct buffer *_buf)
struct buffer_chain *
shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_if_capable)
enum shm_bit_depth desired_bit_depth)
{
pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8;
enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888;
@ -982,8 +982,7 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
static bool have_logged = false;
if (ten_bit_if_capable) {
if (desired_bit_depth == SHM_BITS_10) {
if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) {
pixman_fmt_without_alpha = PIXMAN_x2r10g10b10;
shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010;
@ -1058,3 +1057,13 @@ shm_chain_free(struct buffer_chain *chain)
free(chain);
}
enum shm_bit_depth
shm_chain_bit_depth(const struct buffer_chain *chain)
{
const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha;
return (fmt == PIXMAN_a2r10g10b10 || fmt == PIXMAN_a2b10g10r10)
? SHM_BITS_10
: SHM_BITS_8;
}

5
shm.h
View file

@ -9,6 +9,7 @@
#include <tllist.h>
#include "config.h"
#include "wayland.h"
struct damage;
@ -46,9 +47,11 @@ void shm_set_max_pool_size(off_t max_pool_size);
struct buffer_chain;
struct buffer_chain *shm_chain_new(
struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_it_if_capable);
enum shm_bit_depth desired_bit_depth);
void shm_chain_free(struct buffer_chain *chain);
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain);
/*
* Returns a single buffer.
*

View file

@ -110,10 +110,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
term->sixel.image.height = 0;
term->sixel.image.alloc_height = 0;
term->sixel.image.bottom_pixel = 0;
term->sixel.linear_blending = render_do_linear_blending(term);
term->sixel.linear_blending = wayl_do_linear_blending(term->wl, term->conf);
term->sixel.pixman_fmt = PIXMAN_a8r8g8b8;
if (term->conf->tweak.surface_bit_depth == SHM_10_BIT) {
if (term->conf->tweak.surface_bit_depth == SHM_BITS_10) {
if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) {
term->sixel.use_10bit = true;
term->sixel.pixman_fmt = PIXMAN_a2r10g10b10;

View file

@ -1073,19 +1073,16 @@ reload_fonts(struct terminal *term, bool resize_grid)
options->scaling_filter = conf->tweak.fcft_filter;
options->color_glyphs.format = PIXMAN_a8r8g8b8;
options->color_glyphs.srgb_decode = render_do_linear_blending(term);
options->color_glyphs.srgb_decode =
wayl_do_linear_blending(term->wl, term->conf);
if (conf->tweak.surface_bit_depth == SHM_10_BIT) {
if ((term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) ||
(term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010))
{
/*
* Use a high-res buffer type for emojis. We don't want to
* use an a2r10g0b10 type of surface, since we need more
* than 2 bits for alpha.
*/
options->color_glyphs.format = PIXMAN_rgba_float;
}
if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) {
/*
* Use a high-res buffer type for emojis. We don't want to use
* an a2r10g0b10 type of surface, since we need more than 2
* bits for alpha.
*/
options->color_glyphs.format = PIXMAN_rgba_float;
}
struct fcft_font *fonts[4];
@ -1260,7 +1257,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
goto err;
}
const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT;
const enum shm_bit_depth desired_bit_depth =
conf->tweak.surface_bit_depth == SHM_BITS_AUTO
? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_10 : SHM_BITS_8
: conf->tweak.surface_bit_depth;
const struct color_theme *theme = NULL;
switch (conf->initial_color_theme) {
@ -1353,13 +1353,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.render = {
.chains = {
.grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count,
ten_bit_surfaces),
.search = shm_chain_new(wayl, false, 1 ,ten_bit_surfaces),
.scrollback_indicator = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.render_timer = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.url = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.csd = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.overlay = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
desired_bit_depth),
.search = shm_chain_new(wayl, false, 1 ,desired_bit_depth),
.scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth),
.render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth),
.url = shm_chain_new(wayl, false, 1, desired_bit_depth),
.csd = shm_chain_new(wayl, false, 1, desired_bit_depth),
.overlay = shm_chain_new(wayl, false, 1, desired_bit_depth),
},
.scrollback_lines = conf->scrollback.lines,
.app_sync_updates.timer_fd = app_sync_updates_fd,
@ -1502,7 +1502,7 @@ term_window_configured(struct terminal *term)
xassert(term->window->is_configured);
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
const bool gamma_correct = render_do_linear_blending(term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled");
}
}

View file

@ -2640,3 +2640,10 @@ wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token)
xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf);
}
bool
wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf)
{
return conf->gamma_correct &&
wayl->color_management.img_description != NULL;
}

View file

@ -26,6 +26,7 @@
#include <fcft/fcft.h>
#include <tllist.h>
#include "config.h"
#include "cursor-shape.h"
#include "fdm.h"
@ -539,3 +540,4 @@ bool wayl_get_activation_token(
struct wl_window *win, activation_token_cb_t cb, void *cb_data);
void wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token);
bool wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf);