mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Merge branch 'scroll-damage-performance'
This commit is contained in:
commit
2c7ee09dad
12 changed files with 801 additions and 167 deletions
|
|
@ -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
|
||||
|
|
|
|||
14
config.c
14
config.c
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
1
config.h
1
config.h
|
|
@ -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
2
main.c
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
37
quirks.c
37
quirks.c
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
19
quirks.h
19
quirks.h
|
|
@ -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
346
render.c
|
|
@ -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
|
||||
|
|
|
|||
2
render.h
2
render.h
|
|
@ -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
514
shm.c
|
|
@ -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
26
shm.h
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?) */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue