Merge branch 'scroll-damage-performance'

This commit is contained in:
Daniel Eklöf 2020-03-29 12:07:53 +02:00
commit 2c7ee09dad
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
12 changed files with 801 additions and 167 deletions

View file

@ -21,6 +21,8 @@
* Spaces no longer removed from zsh font name completions.
* Default key binding for _spawn-terminal_ to ctrl+shift+n.
* Renderer is now much faster with interactive scrolling
(https://codeberg.org/dnkl/foot/issues/4)
### Deprecated

View file

@ -22,6 +22,7 @@
#include "wayland.h"
#define ALEN(v) (sizeof(v) / sizeof(v[0]))
#define min(x, y) ((x) < (y) ? (x) : (y))
static const uint32_t default_foreground = 0xdcdccc;
static const uint32_t default_background = 0x111111;
@ -636,6 +637,18 @@ parse_section_tweak(
LOG_WARN("tweak: delayed-render-upper=%lu", ns);
}
else if (strcmp(key, "max-shm-pool-size-mb") == 0) {
unsigned long mb;
if (!str_to_ulong(value, 10, &mb)) {
LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value);
return false;
}
conf->tweak.max_shm_pool_size = min(mb * 1024 * 1024, INT32_MAX);
LOG_WARN("tweak: max-shm-pool-size=%lu bytes",
conf->tweak.max_shm_pool_size);
}
else {
LOG_ERR("%s:%u: invalid key: %s", path, lineno, key);
return false;
@ -906,6 +919,7 @@ config_load(struct config *conf, const char *conf_path)
.tweak = {
.delayed_render_lower_ns = 500000, /* 0.5ms */
.delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */
.max_shm_pool_size = 512 * 1024 * 1024,
},
};

View file

@ -78,6 +78,7 @@ struct config {
struct {
uint64_t delayed_render_lower_ns;
uint64_t delayed_render_upper_ns;
off_t max_shm_pool_size;
} tweak;
};

2
main.c
View file

@ -350,6 +350,8 @@ main(int argc, char *const *argv)
} while (errno == ERANGE);
}
shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
if ((fdm = fdm_init()) == NULL)
goto out;

View file

@ -2,6 +2,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define LOG_MODULE "quirks"
#define LOG_ENABLE_DBG 0
@ -12,19 +13,6 @@
static bool
is_weston(void)
{
/*
* On weston (8.0), synchronized subsurfaces aren't updated
* correctly.
* They appear to render once, but after that, updates are
* sporadic. Sometimes they update, most of the time they
* don't.
*
* Adding explicit parent surface commits right after the
* subsurface commit doesn't help (and would be useless anyway,
* since it would defeat the purpose of having the subsurface
* synchronized in the first place).
*/
static bool is_weston = false;
static bool initialized = false;
@ -79,3 +67,26 @@ quirk_weston_csd_off(struct terminal *term)
for (int i = 0; i < ALEN(term->window->csd.surface); i++)
quirk_weston_subsurface_desync_off(term->window->csd.sub_surface[i]);
}
void
quirk_kde_damage_before_attach(struct wl_surface *surface)
{
static bool is_kde = false;
static bool initialized = false;
if (!initialized) {
initialized = true;
const char *cur_desktop = getenv("XDG_CURRENT_DESKTOP");
if (cur_desktop != NULL)
is_kde = strcasestr(cur_desktop, "kde") != NULL;
if (is_kde)
LOG_WARN("applying wl_surface_damage_buffer() workaround for KDE");
}
if (!is_kde)
return;
wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
}

View file

@ -4,9 +4,28 @@
#include "terminal.h"
/*
* On weston (8.0), synchronized subsurfaces aren't updated correctly.
* They appear to render once, but after that, updates are
* sporadic. Sometimes they update, most of the time they don't.
*
* Adding explicit parent surface commits right after the subsurface
* commit doesn't help (and would be useless anyway, since it would
* defeat the purpose of having the subsurface synchronized in the
* first place).
*/
void quirk_weston_subsurface_desync_on(struct wl_subsurface *sub);
void quirk_weston_subsurface_desync_off(struct wl_subsurface *sub);
/* Shortcuts to call desync_{on,off} on all CSD subsurfaces */
void quirk_weston_csd_on(struct terminal *term);
void quirk_weston_csd_off(struct terminal *term);
/*
* KDE discards all previous damage when a buffer is attached to a
* surface. Thus, if you have recorded damage before you call
* wl_surface_attach(), call this function to record a full buffer
* damage.
*/
void quirk_kde_damage_before_attach(struct wl_surface *surface);

346
render.c
View file

@ -22,6 +22,9 @@
#include "selection.h"
#include "shm.h"
#define TIME_FRAME_RENDERING 0
#define TIME_SCROLL_DAMAGE 0
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
@ -475,56 +478,228 @@ draw_cursor:
return cell_cols;
}
static void
render_margin(struct terminal *term, struct buffer *buf, int start_line, int end_line,
bool top, bool bottom)
{
/* Fill area outside the cell grid with the default background color */
const int rmargin = term->width - term->margins.right;
const int bmargin = term->height - term->margins.bottom;
const int line_count = end_line - start_line;
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
if (term->is_searching)
pixman_color_dim(&bg);
if (top) {
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 1,
&(pixman_rectangle16_t){0, 0, term->width, term->margins.top});
wl_surface_damage_buffer(
term->window->surface, 0, 0, term->width, term->margins.top);
}
if (bottom) {
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 1,
&(pixman_rectangle16_t){0, bmargin, term->width, term->margins.bottom});
wl_surface_damage_buffer(
term->window->surface, 0, bmargin, term->width, term->margins.bottom);
}
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 2,
(pixman_rectangle16_t[]){
/* Left */
{0, term->margins.top + start_line * term->cell_height,
term->margins.left, line_count * term->cell_height},
/* Right */
{rmargin, term->margins.top + start_line * term->cell_height,
term->margins.right, line_count * term->cell_height},
});
/* Left */
wl_surface_damage_buffer(
term->window->surface,
0, term->margins.top + start_line * term->cell_height,
term->margins.left, line_count * term->cell_height);
/* Right */
wl_surface_damage_buffer(
term->window->surface,
rmargin, term->margins.top + start_line * term->cell_height,
term->margins.right, line_count * term->cell_height);
}
static void
grid_render_scroll(struct terminal *term, struct buffer *buf,
const struct damage *dmg)
{
int dst_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height;
int src_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
int height = (dmg->scroll.region.end - dmg->scroll.region.start - dmg->scroll.lines) * term->cell_height;
LOG_DBG("damage: SCROLL: %d-%d by %d lines (dst-y: %d, src-y: %d, "
"height: %d, stride: %d, mmap-size: %zu)",
dmg->scroll.region.start, dmg->scroll.region.end,
dmg->scroll.lines,
dst_y, src_y, height, buf->stride,
buf->size);
LOG_DBG(
"damage: SCROLL: %d-%d by %d lines",
dmg->scroll.region.start, dmg->scroll.region.end, dmg->scroll.lines);
if (height > 0) {
if (height <= 0)
return;
#if TIME_SCROLL_DAMAGE
struct timeval start_time;
gettimeofday(&start_time, NULL);
#endif
int dst_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height;
int src_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
/*
* SHM scrolling can be *much* faster, but it depends on how many
* lines we're scrolling, and how much repairing we need to do.
*
* In short, scrolling a *large* number of rows is faster with a
* memmove, while scrolling a *small* number of lines is faster
* with SHM scrolling.
*
* However, since we need to restore the scrolling regions when
* SHM scrolling, we also need to take this into account.
*
* Finally, we also have to restore the window margins, and this
* is a *huge* performance hit when scrolling a large number of
* lines (in addition to the sloweness of SHM scrolling as
* method).
*
* So, we need to figure out when to SHM scroll, and when to
* memmove.
*
* For now, assume that the both methods perform rougly the same,
* given an equal number of bytes to move/allocate, and use the
* method that results in the least amount of bytes to touch.
*
* Since number of lines directly translates to bytes, we can
* simply count lines.
*
* SHM scrolling needs to first "move" (punch hole + allocate)
* dmg->scroll.lines number of lines, and then we need to restore
* the bottom scroll region.
*
* If the total number of lines is less than half the screen - use
* SHM. Otherwise use memmove.
*/
bool try_shm_scroll =
shm_can_scroll(buf) && (
dmg->scroll.lines +
dmg->scroll.region.start +
(term->rows - dmg->scroll.region.end)) < term->rows / 2;
bool did_shm_scroll = false;
//try_shm_scroll = false;
//try_shm_scroll = true;
if (try_shm_scroll) {
did_shm_scroll = shm_scroll(
term->wl->shm, buf, dmg->scroll.lines * term->cell_height,
term->margins.top, dmg->scroll.region.start * term->cell_height,
term->margins.bottom, (term->rows - dmg->scroll.region.end) * term->cell_height);
}
if (did_shm_scroll) {
/* Restore margins */
render_margin(
term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows,
true, true);
} else {
/* Fallback for when we either cannot do SHM scrolling, or it failed */
uint8_t *raw = buf->mmapped;
memmove(raw + dst_y * buf->stride,
raw + src_y * buf->stride,
height * buf->stride);
wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height);
}
#if TIME_SCROLL_DAMAGE
struct timeval end_time;
gettimeofday(&end_time, NULL);
struct timeval memmove_time;
timersub(&end_time, &start_time, &memmove_time);
LOG_INFO("scrolled %dKB (%d lines) using %s in %lds %ldus",
height * buf->stride / 1024, dmg->scroll.lines,
did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
memmove_time.tv_sec, memmove_time.tv_usec);
#endif
wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y,
term->width - term->margins.left - term->margins.right, height);
}
static void
grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
const struct damage *dmg)
{
int src_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height;
int dst_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
int height = (dmg->scroll.region.end - dmg->scroll.region.start - dmg->scroll.lines) * term->cell_height;
LOG_DBG("damage: SCROLL REVERSE: %d-%d by %d lines (dst-y: %d, src-y: %d, "
"height: %d, stride: %d, mmap-size: %zu)",
dmg->scroll.region.start, dmg->scroll.region.end,
dmg->scroll.lines,
dst_y, src_y, height, buf->stride,
buf->size);
LOG_DBG(
"damage: SCROLL REVERSE: %d-%d by %d lines"m
dmg->scroll.region.start, dmg->scroll.region.end, dmg->scroll.lines);
if (height > 0) {
if (height <= 0)
return;
#if TIME_SCROLL_DAMAGE
struct timeval start_time;
gettimeofday(&start_time, NULL);
#endif
int src_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height;
int dst_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
bool try_shm_scroll =
shm_can_scroll(buf) && (
dmg->scroll.lines +
dmg->scroll.region.start +
(term->rows - dmg->scroll.region.end)) < term->rows / 2;
bool did_shm_scroll = false;
if (try_shm_scroll) {
did_shm_scroll = shm_scroll(
term->wl->shm, buf, -dmg->scroll.lines * term->cell_height,
term->margins.top, dmg->scroll.region.start * term->cell_height,
term->margins.bottom, (term->rows - dmg->scroll.region.end) * term->cell_height);
}
if (did_shm_scroll) {
/* Restore margins */
render_margin(
term, buf, dmg->scroll.region.start, dmg->scroll.region.start + dmg->scroll.lines,
true, true);
} else {
/* Fallback for when we either cannot do SHM scrolling, or it failed */
uint8_t *raw = buf->mmapped;
memmove(raw + dst_y * buf->stride,
raw + src_y * buf->stride,
height * buf->stride);
wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height);
}
#if TIME_SCROLL_DAMAGE
struct timeval end_time;
gettimeofday(&end_time, NULL);
struct timeval memmove_time;
timersub(&end_time, &start_time, &memmove_time);
LOG_INFO("scrolled REVERSE %dKB (%d lines) using %s in %lds %ldus",
height * buf->stride / 1024, dmg->scroll.lines,
did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
memmove_time.tv_sec, memmove_time.tv_usec);
#endif
wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y,
term->width - term->margins.left - term->margins.right, height);
}
static void
@ -739,7 +914,7 @@ render_csd_title(struct terminal *term)
unsigned long cookie = shm_cookie_csd(term, CSD_SURF_TITLE);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
term->wl->shm, info.width, info.height, cookie, false);
uint32_t _color = term->colors.default_fg;
uint16_t alpha = 0xffff;
@ -770,7 +945,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx)
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
term->wl->shm, info.width, info.height, cookie, false);
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
render_csd_part(term, surf, buf, info.width, info.height, &color);
@ -939,7 +1114,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx)
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
term->wl->shm, info.width, info.height, cookie, false);
uint32_t _color;
uint16_t alpha = 0xffff;
@ -1056,8 +1231,6 @@ grid_render(struct terminal *term)
if (term->is_shutting_down)
return;
#define TIME_FRAME_RENDERING 0
#if TIME_FRAME_RENDERING
struct timeval start_time;
gettimeofday(&start_time, NULL);
@ -1068,14 +1241,9 @@ grid_render(struct terminal *term)
unsigned long cookie = shm_cookie_grid(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, term->width, term->height, cookie);
term->wl->shm, term->width, term->height, cookie, true);
wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0);
pixman_image_t *pix = buf->pix;
pixman_region16_t clip;
pixman_region_init_rect(&clip, term->margins.left, term->margins.top, term->cols * term->cell_width, term->rows * term->cell_height);
pixman_image_set_clip_region(pix, &clip);
pixman_image_set_clip_region(buf->pix, NULL);
/* If we resized the window, or is flashing, or just stopped flashing */
if (term->render.last_buf != buf ||
@ -1091,7 +1259,10 @@ grid_render(struct terminal *term)
{
static bool has_warned = false;
if (!has_warned) {
LOG_WARN("it appears your Wayland compositor does not support buffer re-use for SHM clients; expect lower performance.");
LOG_WARN(
"it appears your Wayland compositor does not support "
"buffer re-use for SHM clients; expect lower "
"performance.");
has_warned = true;
}
@ -1100,35 +1271,8 @@ grid_render(struct terminal *term)
}
else {
/* Fill area outside the cell grid with the default background color */
int rmargin = term->width - term->margins.right;
int bmargin = term->height - term->margins.bottom;
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
if (term->is_searching)
pixman_color_dim(&bg);
pixman_image_set_clip_region(pix, NULL);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, pix, &bg, 4,
(pixman_rectangle16_t[]){
{0, 0, term->width, term->margins.top}, /* Top */
{0, 0, term->margins.left, term->height}, /* Left */
{rmargin, 0, term->margins.right, term->height}, /* Right */
{0, bmargin, term->width, term->margins.bottom}}); /* Bottom */
pixman_image_set_clip_region(pix, &clip);
wl_surface_damage_buffer(
term->window->surface, 0, 0, term->width, term->margins.top);
wl_surface_damage_buffer(
term->window->surface, 0, 0, term->margins.left, term->height);
wl_surface_damage_buffer(
term->window->surface, rmargin, 0, term->margins.right, term->height);
wl_surface_damage_buffer(
term->window->surface, 0, bmargin, term->width, term->margins.bottom);
/* Force a full grid refresh */
tll_free(term->grid->scroll_damage);
render_margin(term, buf, 0, term->rows, true, true);
term_damage_view(term);
}
@ -1137,6 +1281,14 @@ grid_render(struct terminal *term)
term->render.was_searching = term->is_searching;
}
/* Set clip region to prevent cells from overflowing into the margins */
pixman_region16_t clip;
pixman_region_init_rect(
&clip,
term->margins.left, term->margins.top,
term->cols * term->cell_width, term->rows * term->cell_height);
pixman_image_set_clip_region(buf->pix, &clip);
/* Erase old cursor (if we rendered a cursor last time) */
if (term->render.last_cursor.cell != NULL) {
@ -1147,7 +1299,7 @@ grid_render(struct terminal *term)
/* If cell is already dirty, it will be rendered anyway */
if (cell->attrs.clean) {
cell->attrs.clean = 0;
int cols = render_cell(term, pix, cell, at.col, at.row, false);
int cols = render_cell(term, buf->pix, cell, at.col, at.row, false);
wl_surface_damage_buffer(
term->window->surface,
@ -1181,6 +1333,9 @@ grid_render(struct terminal *term)
tll_remove(term->grid->scroll_damage, it);
}
/* Reset clip region since scrolling may have instantiated a new pixman image */
pixman_image_set_clip_region(buf->pix, &clip);
if (term->render.workers.count > 0) {
term->render.workers.buf = buf;
@ -1220,7 +1375,7 @@ grid_render(struct terminal *term)
if (!row->dirty)
continue;
render_row(term, pix, row, r);
render_row(term, buf->pix, row, r);
row->dirty = false;
wl_surface_damage_buffer(
@ -1274,7 +1429,7 @@ grid_render(struct terminal *term)
cell->attrs.clean = 0;
term->render.last_cursor.cell = cell;
int cols_updated = render_cell(
term, pix, cell, term->cursor.point.col, view_aligned_row, true);
term, buf->pix, cell, term->cursor.point.col, view_aligned_row, true);
wl_surface_damage_buffer(
term->window->surface,
@ -1283,13 +1438,14 @@ grid_render(struct terminal *term)
cols_updated * term->cell_width, term->cell_height);
}
render_sixel_images(term, pix);
render_sixel_images(term, buf->pix);
if (term->flash.active) {
/* Note: alpha is pre-computed in each color component */
/* TODO: dim while searching */
pixman_image_set_clip_region(buf->pix, NULL);
pixman_image_fill_rectangles(
PIXMAN_OP_OVER, pix,
PIXMAN_OP_OVER, buf->pix,
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
1, &(pixman_rectangle16_t){0, 0, term->width, term->height});
@ -1333,6 +1489,8 @@ grid_render(struct terminal *term)
}
}
wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0);
quirk_kde_damage_before_attach(term->window->surface);
wl_surface_commit(term->window->surface);
#if TIME_FRAME_RENDERING
@ -1370,7 +1528,7 @@ render_search_box(struct terminal *term)
size_t glyph_offset = term->render.search_glyph_offset;
unsigned long cookie = shm_cookie_search(term);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie, false);
/* Background - yellow on empty/match, red on mismatch */
pixman_color_t color = color_hex_to_pixman(
@ -1446,6 +1604,24 @@ render_search_box(struct terminal *term)
quirk_weston_subsurface_desync_off(term->window->search_sub_surface);
}
static void
render_update_title(struct terminal *term)
{
/* TODO: figure out what the limit actually is */
static const size_t max_len = 100;
const char *title = term->window_title != NULL ? term->window_title : "foot";
char *copy = NULL;
if (strlen(title) > max_len) {
copy = strndup(title, max_len);
title = copy;
}
xdg_toplevel_set_title(term->window->xdg_toplevel, title);
free(copy);
}
static void
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
@ -1458,10 +1634,12 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
bool grid = term->render.pending.grid;
bool csd = term->render.pending.csd;
bool search = term->render.pending.search;
bool title = term->render.pending.title;
term->render.pending.grid = false;
term->render.pending.csd = false;
term->render.pending.search = false;
term->render.pending.title = false;
if (csd && term->window->use_csd == CSD_YES) {
quirk_weston_csd_on(term);
@ -1469,6 +1647,9 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
quirk_weston_csd_off(term);
}
if (title)
render_update_title(term);
if (search && term->is_searching)
render_search_box(term);
@ -1801,10 +1982,12 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
bool grid = term->render.refresh.grid;
bool csd = term->render.refresh.csd;
bool search = term->render.refresh.search;
bool title = term->render.refresh.title;
term->render.refresh.grid = false;
term->render.refresh.csd = false;
term->render.refresh.search = false;
term->render.refresh.title = false;
if (term->window->frame_callback == NULL) {
if (csd && term->window->use_csd == CSD_YES) {
@ -1812,6 +1995,8 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
render_csd(term);
quirk_weston_csd_off(term);
}
if (title)
render_update_title(term);
if (search)
render_search_box(term);
if (grid)
@ -1821,6 +2006,7 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
term->render.pending.grid |= grid;
term->render.pending.csd |= csd;
term->render.pending.search |= search;
term->render.pending.title |= title;
}
}
@ -1835,21 +2021,9 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
}
void
render_set_title(struct terminal *term, const char *_title)
render_refresh_title(struct terminal *term)
{
/* TODO: figure out what the limit actually is */
static const size_t max_len = 100;
const char *title = _title;
char *copy = NULL;
if (strlen(title) > max_len) {
copy = strndup(_title, max_len);
title = copy;
}
xdg_toplevel_set_title(term->window->xdg_toplevel, title);
free(copy);
term->render.refresh.title = true;
}
void

View file

@ -12,10 +12,10 @@ void render_destroy(struct renderer *renderer);
bool render_resize(struct terminal *term, int width, int height);
bool render_resize_force(struct terminal *term, int width, int height);
void render_set_title(struct terminal *term, const char *title);
void render_refresh(struct terminal *term);
void render_refresh_csd(struct terminal *term);
void render_refresh_search(struct terminal *term);
void render_refresh_title(struct terminal *term);
bool render_xcursor_set(struct terminal *term);
struct render_worker_context {

514
shm.c
View file

@ -1,11 +1,15 @@
#include "shm.h"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <linux/mman.h>
#include <linux/memfd.h>
#include <fcntl.h>
@ -19,21 +23,85 @@
#define LOG_ENABLE_DBG 0
#include "log.h"
#define TIME_SCROLL 0
/*
* Maximum memfd size allowed.
*
* On 64-bit, we could in theory use up to 2GB (wk_shm_create_pool()
* is limited to int32_t), since we never mmap() the entire region.
*
* The compositor is different matter - it needs to mmap() the entire
* range, and *keep* the mapping for as long as is has buffers
* referencing it (thus - always). And if we open multiple terminals,
* then the required address space multiples...
*
* That said, 128TB (the total amount of available user address space
* on 64-bit) is *a lot*; we can fit 67108864 2GB memfds into
* that. But, let's be conservative for now.
*
* On 32-bit the available address space is too small and SHM
* scrolling is disabled.
*
* Note: this is the _default_ size. It can be overridden by calling
* shm_set_max_pool_size();
*/
static off_t max_pool_size = 512 * 1024 * 1024;
static tll(struct buffer) buffers;
static bool can_punch_hole = false;
static bool can_punch_hole_initialized = false;
void
shm_set_max_pool_size(off_t _max_pool_size)
{
max_pool_size = _max_pool_size;
}
static void
buffer_destroy_dont_close(struct buffer *buf)
{
if (buf->pix != NULL)
pixman_image_unref(buf->pix);
if (buf->wl_buf != NULL)
wl_buffer_destroy(buf->wl_buf);
buf->pix = NULL;
buf->wl_buf = NULL;
buf->mmapped = NULL;
}
static void
buffer_destroy(struct buffer *buf)
{
if (buf->pix != NULL)
pixman_image_unref(buf->pix);
wl_buffer_destroy(buf->wl_buf);
munmap(buf->mmapped, buf->size);
buffer_destroy_dont_close(buf);
if (buf->real_mmapped != MAP_FAILED)
munmap(buf->real_mmapped, buf->mmap_size);
if (buf->pool != NULL)
wl_shm_pool_destroy(buf->pool);
if (buf->fd >= 0)
close(buf->fd);
buf->real_mmapped = MAP_FAILED;
buf->pool = NULL;
buf->fd = -1;
}
void
shm_fini(void)
{
tll_foreach(buffers, it) {
buffer_destroy(&it->item);
tll_remove(buffers, it);
}
}
static void
buffer_release(void *data, struct wl_buffer *wl_buffer)
{
struct buffer *buffer = data;
LOG_DBG("release: cookie=%lx (buf=%p)", buffer->cookie, buffer);
assert(buffer->wl_buf == wl_buffer);
assert(buffer->busy);
buffer->busy = false;
@ -43,8 +111,70 @@ static const struct wl_buffer_listener buffer_listener = {
.release = &buffer_release,
};
static size_t
page_size(void)
{
static size_t size = 0;
if (size == 0) {
size = sysconf(_SC_PAGE_SIZE);
if (size < 0) {
LOG_ERRNO("failed to get page size");
size = 4096;
}
}
assert(size > 0);
return size;
}
static bool
instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
{
assert(buf->fd >= 0);
assert(buf->mmapped == NULL);
assert(buf->wl_buf == NULL);
assert(buf->pix == NULL);
void *mmapped = MAP_FAILED;
struct wl_buffer *wl_buf = NULL;
pixman_image_t *pix = NULL;
mmapped = (uint8_t *)buf->real_mmapped + new_offset;
wl_buf = wl_shm_pool_create_buffer(
buf->pool, new_offset, buf->width, buf->height, buf->stride, WL_SHM_FORMAT_ARGB8888);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
goto err;
}
/* One pixman image for each worker thread (do we really need multiple?) */
pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, buf->width, buf->height, (uint32_t *)mmapped, buf->stride);
if (pix == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
}
buf->offset = new_offset;
buf->mmapped = mmapped;
buf->wl_buf = wl_buf;
buf->pix = pix;
wl_buffer_add_listener(wl_buf, &buffer_listener, buf);
return true;
err:
if (pix != NULL)
pixman_image_unref(pix);
if (wl_buf != NULL)
wl_buffer_destroy(wl_buf);
abort();
return false;
}
struct buffer *
shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie)
shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable)
{
/* Purge buffers marked for purging */
tll_foreach(buffers, it) {
@ -73,7 +203,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie)
continue;
if (!it->item.busy) {
LOG_DBG("cookie=%lx: re-using buffer from cache", cookie);
LOG_DBG("cookie=%lx: re-using buffer from cache (buf=%p)",
cookie, &it->item);
it->item.busy = true;
it->item.purge = false;
return &it->item;
@ -106,71 +237,83 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie)
*/
int pool_fd = -1;
void *mmapped = MAP_FAILED;
size_t size = 0;
const int stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, width);
const size_t size = stride * height;
void *real_mmapped = MAP_FAILED;
struct wl_shm_pool *pool = NULL;
struct wl_buffer *buf = NULL;
pixman_image_t *pix = NULL;
LOG_DBG("cookie=%lx: allocating new buffer: %zu KB", cookie, size / 1024);
/* Backing memory for SHM */
pool_fd = memfd_create("foot-wayland-shm-buffer-pool", MFD_CLOEXEC);
pool_fd = memfd_create("foot-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (pool_fd == -1) {
LOG_ERRNO("failed to create SHM backing memory file");
goto err;
}
const int stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, width);
#if defined(__i386__)
off_t initial_offset = 0;
off_t memfd_size = size;
#else
off_t initial_offset = scrollable && max_pool_size > 0 ? (max_pool_size / 4) & ~(page_size() - 1) : 0;
off_t memfd_size = scrollable && max_pool_size > 0 ? max_pool_size : size;
#endif
/* Total size */
size = stride * height;
LOG_DBG("cookie=%lx: allocating new buffer: %zu KB", cookie, size / 1024);
LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, initial_offset);
int err = EINTR;
while (err == EINTR)
err = posix_fallocate(pool_fd, 0, size);
if (err != 0) {
static bool failure_logged = false;
if (ftruncate(pool_fd, memfd_size) == -1) {
LOG_ERRNO("failed to set size of SHM backing memory file");
goto err;
}
if (!failure_logged) {
failure_logged = true;
LOG_ERRNO_P("failed to fallocate %zu bytes", err, size);
if (!can_punch_hole_initialized) {
can_punch_hole_initialized = true;
can_punch_hole = fallocate(
pool_fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, 1) == 0;
if (!can_punch_hole) {
LOG_WARN(
"fallocate(FALLOC_FL_PUNCH_HOLE) not "
"supported (%s): expect lower performance", strerror(errno));
}
}
if (ftruncate(pool_fd, size) == -1) {
LOG_ERRNO("failed to truncate SHM pool");
if (scrollable && !can_punch_hole) {
initial_offset = 0;
memfd_size = size;
scrollable = false;
if (ftruncate(pool_fd, memfd_size) < 0) {
LOG_ERRNO("failed to set size of SHM backing memory file");
goto err;
}
}
mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, pool_fd, 0);
if (mmapped == MAP_FAILED) {
real_mmapped = mmap(
NULL, memfd_size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_UNINITIALIZED, pool_fd, 0);
if (real_mmapped == MAP_FAILED) {
LOG_ERRNO("failed to mmap SHM backing memory file");
goto err;
}
pool = wl_shm_create_pool(shm, pool_fd, size);
/* Seal file - we no longer allow any kind of resizing */
/* TODO: wayland mmaps(PROT_WRITE), for some unknown reason, hence we cannot use F_SEAL_FUTURE_WRITE */
if (fcntl(pool_fd, F_ADD_SEALS,
F_SEAL_GROW | F_SEAL_SHRINK | /*F_SEAL_FUTURE_WRITE |*/ F_SEAL_SEAL) < 0)
{
LOG_ERRNO("failed to seal SHM backing memory file");
goto err;
}
pool = wl_shm_create_pool(shm, pool_fd, memfd_size);
if (pool == NULL) {
LOG_ERR("failed to create SHM pool");
goto err;
}
buf = wl_shm_pool_create_buffer(
pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
if (buf == NULL) {
LOG_ERR("failed to create SHM buffer");
goto err;
}
/* We use the entire pool for our single buffer */
wl_shm_pool_destroy(pool); pool = NULL;
close(pool_fd); pool_fd = -1;
/* One pixman image for each worker thread (do we really need multiple?) */
pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, width, height, (uint32_t *)mmapped, stride);
/* Push to list of available buffers, but marked as 'busy' */
tll_push_back(
buffers,
@ -181,43 +324,292 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie)
.stride = stride,
.busy = true,
.size = size,
.mmapped = mmapped,
.wl_buf = buf,
.pix = pix}
.fd = pool_fd,
.pool = pool,
.scrollable = scrollable,
.real_mmapped = real_mmapped,
.mmap_size = memfd_size,
.offset = 0}
)
);
struct buffer *ret = &tll_back(buffers);
wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret);
if (!instantiate_offset(shm, ret, initial_offset))
goto err;
return ret;
err:
if (pix != NULL)
pixman_image_unref(pix);
if (buf != NULL)
wl_buffer_destroy(buf);
if (pool != NULL)
wl_shm_pool_destroy(pool);
if (real_mmapped != MAP_FAILED)
munmap(real_mmapped, memfd_size);
if (pool_fd != -1)
close(pool_fd);
if (mmapped != MAP_FAILED)
munmap(mmapped, size);
/* We don't handle this */
abort();
return NULL;
}
void
shm_fini(void)
bool
shm_can_scroll(const struct buffer *buf)
{
tll_foreach(buffers, it) {
buffer_destroy(&it->item);
tll_remove(buffers, it);
}
#if defined(__i386__)
/* Not enough virtual address space in 32-bit */
return false;
#else
return can_punch_hole && max_pool_size > 0 && buf->scrollable;
#endif
}
void
static bool
wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
{
/* We don't allow overlapping offsets */
off_t diff __attribute__((unused)) =
new_offset < buf->offset ? buf->offset - new_offset : new_offset - buf->offset;
assert(diff > buf->size);
memcpy((uint8_t *)buf->real_mmapped + new_offset, buf->mmapped, buf->size);
off_t trim_ofs, trim_len;
if (new_offset > buf->offset) {
/* Trim everything *before* the new offset */
trim_ofs = 0;
trim_len = new_offset;
} else {
/* Trim everything *after* the new buffer location */
trim_ofs = new_offset + buf->size;
trim_len = buf->mmap_size - trim_ofs;
}
if (fallocate(
buf->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
LOG_ERRNO("failed to trim SHM backing memory file");
return false;
}
/* Re-instantiate pixman+wl_buffer+raw pointersw */
buffer_destroy_dont_close(buf);
return instantiate_offset(shm, buf, new_offset);
}
static bool
shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
assert(can_punch_hole);
assert(buf->busy);
assert(buf->pix);
assert(buf->wl_buf);
assert(buf->fd >= 0);
LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride);
const off_t diff = rows * buf->stride;
assert(rows > 0);
assert(diff < buf->size);
if (buf->offset + diff + buf->size > max_pool_size) {
LOG_DBG("memfd offset wrap around");
if (!wrap_buffer(shm, buf, 0))
goto err;
}
off_t new_offset = buf->offset + diff;
assert(new_offset > buf->offset);
assert(new_offset + buf->size <= max_pool_size);
#if TIME_SCROLL
struct timeval time1;
gettimeofday(&time1, NULL);
struct timeval time2 = time1;
#endif
if (top_keep_rows > 0) {
/* Copy current 'top' region to its new location */
memmove(
(uint8_t *)buf->mmapped + (top_margin + rows) * buf->stride,
(uint8_t *)buf->mmapped + (top_margin + 0) * buf->stride,
top_keep_rows * buf->stride);
#if TIME_SCROLL
gettimeofday(&time2, NULL);
timersub(&time2, &time1, &tot);
LOG_INFO("memmove (top region): %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
}
/* Destroy old objects (they point to the old offset) */
buffer_destroy_dont_close(buf);
/* Free unused memory - everything up until the new offset */
const off_t trim_ofs = 0;
const off_t trim_len = new_offset;
if (fallocate(
buf->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
LOG_ERRNO("failed to trim SHM backing memory file");
goto err;
}
#if TIME_SCROLL
struct timeval time3;
gettimeofday(&time3, NULL);
timersub(&time3, &time2, &tot);
LOG_INFO("PUNCH HOLE: %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
/* Re-instantiate pixman+wl_buffer+raw pointersw */
bool ret = instantiate_offset(shm, buf, new_offset);
#if TIME_SCROLL
struct timeval time4;
gettimeofday(&time4, NULL);
timersub(&time4, &time3, &tot);
LOG_INFO("instantiate offset: %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
if (ret && bottom_keep_rows > 0) {
/* Copy 'bottom' region to its new location */
memmove(
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + bottom_keep_rows) * buf->stride,
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + rows + bottom_keep_rows) * buf->stride,
bottom_keep_rows * buf->stride);
#if TIME_SCROLL
struct timeval time5;
gettimeofday(&time5, NULL);
timersub(&time5, &time4, &tot);
LOG_INFO("memmove (bottom region): %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
}
return ret;
err:
abort();
return false;
}
static bool
shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
assert(rows > 0);
const off_t diff = rows * buf->stride;
if (diff > buf->offset) {
LOG_DBG("memfd offset reverse wrap-around");
if (!wrap_buffer(shm, buf, (max_pool_size - buf->size) & ~(page_size() - 1)))
goto err;
}
off_t new_offset = buf->offset - diff;
assert(new_offset < buf->offset);
assert(new_offset <= max_pool_size);
#if TIME_SCROLL
struct timeval time0;
gettimeofday(&time0, NULL);
struct timeval tot;
struct timeval time1 = time0;
#endif
if (bottom_keep_rows > 0) {
/* Copy 'bottom' region to its new location */
memmove(
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + rows + bottom_keep_rows) * buf->stride,
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + bottom_keep_rows) * buf->stride,
bottom_keep_rows * buf->stride);
#if TIME_SCROLL
gettimeofday(&time1, NULL);
timersub(&time1, &time0, &tot);
LOG_INFO("memmove (bottom region): %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
}
/* Destroy old objects (they point to the old offset) */
buffer_destroy_dont_close(buf);
/* Free unused memory - everything after the relocated buffer */
const off_t trim_ofs = new_offset + buf->size;
const off_t trim_len = buf->mmap_size - trim_ofs;
if (fallocate(
buf->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
LOG_ERRNO("failed to trim SHM backing memory");
goto err;
}
#if TIME_SCROLL
struct timeval time2;
gettimeofday(&time2, NULL);
timersub(&time2, &time1, &tot);
LOG_INFO("fallocate: %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
/* Re-instantiate pixman+wl_buffer+raw pointers */
bool ret = instantiate_offset(shm, buf, new_offset);
#if TIME_SCROLL
struct timeval time3;
gettimeofday(&time3, NULL);
timersub(&time3, &time2, &tot);
LOG_INFO("instantiate offset: %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
if (ret && top_keep_rows > 0) {
/* Copy current 'top' region to its new location */
memmove(
(uint8_t *)buf->mmapped + (top_margin + 0) * buf->stride,
(uint8_t *)buf->mmapped + (top_margin + rows) * buf->stride,
top_keep_rows * buf->stride);
#if TIME_SCROLL
struct timeval time4;
gettimeofday(&time4, NULL);
timersub(&time4, &time2, &tot);
LOG_INFO("memmove (top region): %lds %ldus", tot.tv_sec, tot.tv_usec);
#endif
}
return ret;
err:
abort();
return false;
}
bool
shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
if (!shm_can_scroll(buf))
return false;
assert(rows != 0);
return rows > 0
? shm_scroll_forward(shm, buf, rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows)
: shm_scroll_reverse(shm, buf, -rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows);
}
void
shm_purge(struct wl_shm *shm, unsigned long cookie)
{
LOG_DBG("cookie=%lx: purging all buffers", cookie);

26
shm.h
View file

@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
#include <pixman.h>
#include <wayland-client.h>
@ -13,20 +14,35 @@ struct buffer {
int height;
int stride;
bool purge;
bool busy;
size_t size;
void *mmapped;
size_t size; /* Buffer size */
void *mmapped; /* Raw data (TODO: rename) */
struct wl_buffer *wl_buf;
pixman_image_t *pix;
/* Internal */
int fd; /* memfd */
struct wl_shm_pool *pool;
void *real_mmapped; /* Address returned from mmap */
size_t mmap_size; /* Size of mmap (>= size) */
off_t offset; /* Offset into memfd where data begins */
bool scrollable;
bool purge; /* True if this buffer should be destroyed */
};
struct buffer *shm_get_buffer(
struct wl_shm *shm, int width, int height, unsigned long cookie);
struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable);
void shm_fini(void);
void shm_set_max_pool_size(off_t max_pool_size);
bool shm_can_scroll(const struct buffer *buf);
bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows);
void shm_purge(struct wl_shm *shm, unsigned long cookie);
struct terminal;

View file

@ -1960,7 +1960,7 @@ term_set_window_title(struct terminal *term, const char *title)
{
free(term->window_title);
term->window_title = strdup(title);
render_set_title(term, term->window_title);
render_refresh_title(term);
}
void
@ -2069,6 +2069,7 @@ term_enable_app_sync_updates(struct terminal *term)
timerfd_settime(
term->delayed_render_timer.upper_fd, 0,
&(struct itimerspec){{0}}, NULL);
term->delayed_render_timer.is_armed = false;
}
void

View file

@ -336,6 +336,7 @@ struct terminal {
bool grid;
bool csd;
bool search;
bool title;
} refresh;
/* Scheduled for rendering, in the next frame callback */
@ -343,6 +344,7 @@ struct terminal {
bool grid;
bool csd;
bool search;
bool title;
} pending;
int scrollback_lines; /* Number of scrollback lines, from conf (TODO: move out from render struct?) */