From 1224807f503cf6aaef33388af682f754dcbbc3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 20:06:44 +0100 Subject: [PATCH 01/27] shm: new function, shm_scroll() This function "scrolls" the buffer by the specified number of (pixel) rows. The idea is move the image offset by re-sizing the underlying memfd object. I.e. to scroll forward, increase the size of the memfd file, and move the pixman image offset forward (and the Wayland SHM buffer as well). Only increasing the file size would, obviously, cause the memfd file to grow indefinitely. To deal with this, we "punch" a whole from the beginning of the file to the new offset. This frees the associated memory. Thus, while we have a memfd file whose size is (as seen by e.g. fstat()) is ever growing, the actual file size is always the original buffer size. Some notes: * FALLOC_FL_PUNCH_HOLE can be quite slow when the number of used pages to drop is large. * all normal fallocate() usages have been replaced with ftruncate(), as this is *much* faster. fallocate() guarantees subsequent writes wont fail. I.e. it actually reserves (disk) space. While it doesn't allocate on-disk blocks for on-disk files, it *does* zero-initialize the in-memory blocks. And this is slow. ftruncate() doesn't do this. TODO: implement reverse scrolling (i.e. a negative row count). --- shm.c | 267 +++++++++++++++++++++++++++++++++++++++++++--------------- shm.h | 15 +++- 2 files changed, 210 insertions(+), 72 deletions(-) diff --git a/shm.c b/shm.c index a4fc8797..a96bda4f 100644 --- a/shm.c +++ b/shm.c @@ -1,11 +1,13 @@ #include "shm.h" #include +#include #include #include #include #include +#include #include #include #include @@ -19,6 +21,8 @@ #define LOG_ENABLE_DBG 0 #include "log.h" +#define TIME_SCROLL 0 + static tll(struct buffer) buffers; static void @@ -26,14 +30,19 @@ 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); + if (buf->wl_buf != NULL) + wl_buffer_destroy(buf->wl_buf); + if (buf->real_mmapped != MAP_FAILED) + munmap(buf->real_mmapped, buf->mmap_size); + if (buf->fd >= 0) + close(buf->fd); } 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,6 +52,99 @@ static const struct wl_buffer_listener buffer_listener = { .release = &buffer_release, }; +static bool +instantiate_offset(struct wl_shm *shm, struct buffer *buf, size_t new_offset) +{ + assert(buf->fd >= 0); + assert(buf->mmapped == NULL); + assert(buf->real_mmapped == NULL); + assert(buf->wl_buf == NULL); + assert(buf->pix == NULL); + + assert(new_offset >= buf->offset); + + static size_t page_size = 0; + if (page_size == 0) { + page_size = sysconf(_SC_PAGE_SIZE); + if (page_size < 0) { + LOG_ERRNO("failed to get page size"); + page_size = 4096; + } + } + assert(page_size > 0); + + void *real_mmapped = MAP_FAILED; + void *mmapped = MAP_FAILED; + struct wl_shm_pool *pool = NULL; + struct wl_buffer *wl_buf = NULL; + pixman_image_t *pix = NULL; + + /* mmap offset must be page aligned */ + size_t aligned_offset = new_offset & ~(page_size - 1); + size_t page_offset = new_offset & (page_size - 1); + size_t mmap_size = buf->size + page_offset; + + assert(aligned_offset <= new_offset); + assert(mmap_size >= buf->size); + + LOG_DBG("size=%zx, offset=%zx, size-aligned=%zx, offset-aligned=%zx", + buf->size, buf->offset, mmap_size, aligned_offset); + + real_mmapped = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, buf->fd, aligned_offset); + if (real_mmapped == MAP_FAILED) { + LOG_ERRNO("failed to mmap SHM backing memory file"); + goto err; + } + mmapped = real_mmapped + page_offset; + + pool = wl_shm_create_pool(shm, buf->fd, new_offset + buf->size); + if (pool == NULL) { + LOG_ERR("failed to create SHM pool"); + goto err; + } + + wl_buf = wl_shm_pool_create_buffer( + 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; + } + + /* We use the entire pool for our single buffer */ + wl_shm_pool_destroy(pool); pool = NULL; + + /* 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->real_mmapped = real_mmapped; + buf->mmapped = mmapped; + buf->mmap_size = mmap_size; + 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); + if (pool != NULL) + wl_shm_pool_destroy(pool); + if (real_mmapped != MAP_FAILED) + munmap(real_mmapped, mmap_size); + + abort(); + return false; +} + struct buffer * shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) { @@ -73,7 +175,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,13 +209,10 @@ 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; - 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); @@ -121,56 +221,11 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) goto err; } - const int stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, width); - - /* Total size */ - size = stride * height; - LOG_DBG("cookie=%lx: allocating new buffer: %zu KB", cookie, size / 1024); - - int err = EINTR; - while (err == EINTR) - err = posix_fallocate(pool_fd, 0, size); - if (err != 0) { - static bool failure_logged = false; - - if (!failure_logged) { - failure_logged = true; - LOG_ERRNO_P("failed to fallocate %zu bytes", err, size); - } - - if (ftruncate(pool_fd, size) == -1) { - LOG_ERRNO("failed to truncate SHM pool"); - goto err; - } - } - - mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, pool_fd, 0); - if (mmapped == MAP_FAILED) { - LOG_ERRNO("failed to mmap SHM backing memory file"); + if (ftruncate(pool_fd, size) == -1) { + LOG_ERRNO("failed to truncate SHM pool"); goto err; } - pool = wl_shm_create_pool(shm, pool_fd, 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,27 +236,20 @@ 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, + .mmap_size = size, + .offset = 0} ) ); struct buffer *ret = &tll_back(buffers); - wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret); + if (!instantiate_offset(shm, ret, 0)) + 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 (pool_fd != -1) close(pool_fd); - if (mmapped != MAP_FAILED) - munmap(mmapped, size); /* We don't handle this */ abort(); @@ -217,6 +265,89 @@ shm_fini(void) } } +bool +shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) +{ + assert(buf->busy); + assert(buf->pix); + assert(buf->wl_buf); + assert(buf->real_mmapped); + assert(buf->fd >= 0); + + LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride); + + assert(rows > 0); + assert(rows * buf->stride < buf->size); + const size_t new_offset = buf->offset + rows * buf->stride; + +#if TIME_SCROLL + struct timeval time0; + gettimeofday(&time0, NULL); +#endif + + /* Increase file size */ + if (ftruncate(buf->fd, new_offset + buf->size) < 0) { + LOG_ERRNO("failed increase memfd size from %zu -> %zu", + buf->offset + buf->size, new_offset + buf->size); + + /* Instantiate a new buffer and copy over our content */ + struct buffer *fresh_buf = shm_get_buffer(shm, buf->width, buf->height, buf->cookie); + + memcpy(fresh_buf->mmapped, + (const uint8_t *)buf->mmapped + rows * buf->stride, + buf->size - rows * buf->stride); + + buffer_destroy(buf); + *buf = *fresh_buf; + + assert(false); + + /* Mark copied buffer for deletion, but make sure we don't + * free any of its resources */ + fresh_buf->pix = NULL; + fresh_buf->wl_buf = NULL; + fresh_buf->real_mmapped = MAP_FAILED; + fresh_buf->fd = -1; + fresh_buf->purge = true; + return true; + } + +#if TIME_SCROLL + struct timeval time1; + gettimeofday(&time1, NULL); + + struct timeval tot; + timersub(&time1, &time0, &tot); + LOG_INFO("fallocate: %lds %ldus", tot.tv_sec, tot.tv_usec); +#endif + + /* Destroy old objects (they point to the old offset) */ + pixman_image_unref(buf->pix); + wl_buffer_destroy(buf->wl_buf); + munmap(buf->real_mmapped, buf->mmap_size); + + buf->pix = NULL; + buf->wl_buf = NULL; + buf->real_mmapped = buf->mmapped = NULL; + + /* Free unused memory */ + if (fallocate(buf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, new_offset) < 0) { + LOG_ERRNO( + "fallocate(FALLOC_FL_PUNCH_HOLE) not " + "supported: expect lower performance"); + abort(); + } + +#if TIME_SCROLL + struct timeval time2; + gettimeofday(&time2, NULL); + timersub(&time2, &time1, &tot); + LOG_INFO("PUNCH HOLE: %lds %ldus", tot.tv_sec, tot.tv_usec); +#endif + + return instantiate_offset(shm, buf, new_offset); +} + void shm_purge(struct wl_shm *shm, unsigned long cookie) { diff --git a/shm.h b/shm.h index eb9987ed..2b3a9b19 100644 --- a/shm.h +++ b/shm.h @@ -13,20 +13,27 @@ struct buffer { int height; int stride; - bool purge; - bool busy; - size_t size; - void *mmapped; + size_t size; /* Buffer size */ + void *mmapped; /* Raw data */ struct wl_buffer *wl_buf; pixman_image_t *pix; + + /* Internal */ + int fd; /* memfd */ + void *real_mmapped; /* Address returned from mmap */ + size_t mmap_size; /* Size of mmap (>= size) */ + size_t offset; /* Offset into memfd where data begins */ + 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); void shm_fini(void); +bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows); + void shm_purge(struct wl_shm *shm, unsigned long cookie); struct terminal; From ed987b2de799306d6147fb4fe471178d8d127bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 20:22:17 +0100 Subject: [PATCH 02/27] render: use shm_scroll() when we believe it will be faster shm_scroll() is fast when memmove() is slow. That is, when scrolling a *small* amount of lines, shm_scroll() is fast. Conversely, when scrolling a *large* number of lines, memmove() is fast. For now, assume the two methods perform _roughly_ the same given a certain number of bytes they have to touch. This means we should use shm_scroll() when number of scroll lines is less than half the screen. Otherwise we use memmove. Since we need to repair the bottom scroll region after a shm_scroll, those lines are added to the count when determining which method to use. TODO: * Check if it's worth to do shm scrolling when we have a top scroll region. * Do more performance testing with a) various amount of scrolling lines, and b) larger bottom scroll regions. --- render.c | 242 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 187 insertions(+), 55 deletions(-) diff --git a/render.c b/render.c index d8a26060..7189c9ba 100644 --- a/render.c +++ b/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,30 +478,181 @@ draw_cursor: return cell_cols; } +static void +render_margin(struct terminal *term, struct buffer *buf, int start_line, int end_line) +{ + /* 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); + + if (start_line == 0) { + 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 (end_line == term->rows) { + 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[]){ + {0, term->margins.top + start_line * term->cell_height, + term->margins.left, (end_line - start_line) * term->cell_height}, /* Left */ + {rmargin, term->margins.top, term->margins.right, (end_line - start_line) * term->cell_height}, /* Right */ + }); + + wl_surface_damage_buffer( + term->window->surface, 0, term->margins.top, term->margins.left, (end_line - start_line) * term->cell_height); + wl_surface_damage_buffer( + term->window->surface, rmargin, term->margins.top, term->margins.right, (end_line - start_line) * 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 bottom scrolling region + * when SHM scrolling, we also need to take this into account. + * + * Finally, restoring the window margins 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 = + dmg->scroll.region.start == 0 && + dmg->scroll.lines + (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); + + /* + * When SHM scrolling succeeded, the scrolled in area is made + * up of newly allocated, zero-initialized memory. Thus we'll + * need to both copy the bottom scrolling region, and + * re-render the window margins. + * + * This is different from when we scroll with a simple + * memmove, since in that case, the scrolling region and + * margins are *copied*, and thus the original region+margin + * remains in place. + */ + + if (!did_shm_scroll) + LOG_DBG("fast scroll failed"); + } else { + LOG_DBG( + "skipping SHM scroll " + "(region.start=%d, bottom-region-lines=%d, term-lines=%d)", + dmb->scroll.region.start, scroll_region_lines, term->rows); + } + + if (did_shm_scroll) { + + /* Restore bottom scrolling region */ + if (dmg->scroll.region.end < term->rows) { + int src = dmg->scroll.region.end - dmg->scroll.lines; + int dst = dmg->scroll.region.end; + size_t amount = max(0, term->rows - dmg->scroll.region.end); + + LOG_DBG("memmoving %zu lines of scroll region", amount); + + assert(src >= 0); + + uint8_t *raw = buf->mmapped; + memmove(raw + dst * term->cell_height * buf->stride, + raw + src * term->cell_height * buf->stride, + amount * term->cell_height * buf->stride); + } + + /* Restore margins */ + render_margin( + term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows); + } + + /* Fallback for when we either cannot do SHM scrolling, or it failed */ + if (!did_shm_scroll) { 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 @@ -1056,8 +1210,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); @@ -1070,12 +1222,7 @@ grid_render(struct terminal *term) struct buffer *buf = shm_get_buffer( term->wl->shm, term->width, term->height, cookie); - 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 +1238,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 +1250,7 @@ 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 */ + render_margin(term, buf, 0, term->rows); term_damage_view(term); } @@ -1137,6 +1259,13 @@ grid_render(struct terminal *term) term->render.was_searching = term->is_searching; } + 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 +1276,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 +1310,9 @@ grid_render(struct terminal *term) tll_remove(term->grid->scroll_damage, it); } + pixman_image_set_clip_region(buf->pix, &clip); + wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); + if (term->render.workers.count > 0) { term->render.workers.buf = buf; @@ -1220,7 +1352,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 +1406,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 +1415,13 @@ 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_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}); From 3b9be09b065d50cc0d698faf6a855627b5cf86cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 20:32:11 +0100 Subject: [PATCH 03/27] shm: scroll: no need to instantiate a new buffer when ftruncate() fails --- shm.c | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/shm.c b/shm.c index a96bda4f..1d36137b 100644 --- a/shm.c +++ b/shm.c @@ -289,27 +289,7 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) if (ftruncate(buf->fd, new_offset + buf->size) < 0) { LOG_ERRNO("failed increase memfd size from %zu -> %zu", buf->offset + buf->size, new_offset + buf->size); - - /* Instantiate a new buffer and copy over our content */ - struct buffer *fresh_buf = shm_get_buffer(shm, buf->width, buf->height, buf->cookie); - - memcpy(fresh_buf->mmapped, - (const uint8_t *)buf->mmapped + rows * buf->stride, - buf->size - rows * buf->stride); - - buffer_destroy(buf); - *buf = *fresh_buf; - - assert(false); - - /* Mark copied buffer for deletion, but make sure we don't - * free any of its resources */ - fresh_buf->pix = NULL; - fresh_buf->wl_buf = NULL; - fresh_buf->real_mmapped = MAP_FAILED; - fresh_buf->fd = -1; - fresh_buf->purge = true; - return true; + return false; } #if TIME_SCROLL From 7404ace40c1b54db14a95f3f64b9f92104e8024a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 20:36:15 +0100 Subject: [PATCH 04/27] shm: verify the system supports FALLOC_FL_PUNCH_HOLE --- shm.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shm.c b/shm.c index 1d36137b..2f74fc0b 100644 --- a/shm.c +++ b/shm.c @@ -25,6 +25,9 @@ static tll(struct buffer) buffers; +static bool can_punch_hole = false; +static bool can_punch_hole_initialized = false; + static void buffer_destroy(struct buffer *buf) { @@ -226,6 +229,12 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) goto err; } + 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; + } + /* Push to list of available buffers, but marked as 'busy' */ tll_push_back( buffers, @@ -274,6 +283,9 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) assert(buf->real_mmapped); assert(buf->fd >= 0); + if (!can_punch_hole) + return false; + LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride); assert(rows > 0); From 795b0e7ea1cc7098fcbb76858014ffccffbf8624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 21:04:00 +0100 Subject: [PATCH 05/27] shm: print performance warning when FALLOC_FL_PUNCH_HOLE isn't supported --- shm.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shm.c b/shm.c index 2f74fc0b..b9cc325c 100644 --- a/shm.c +++ b/shm.c @@ -233,6 +233,12 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) 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)); + } } /* Push to list of available buffers, but marked as 'busy' */ @@ -324,9 +330,6 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) /* Free unused memory */ if (fallocate(buf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, new_offset) < 0) { - LOG_ERRNO( - "fallocate(FALLOC_FL_PUNCH_HOLE) not " - "supported: expect lower performance"); abort(); } From 5ffee087481ddf3ad6e3b8e74c6acd3f0d736d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 19:31:05 +0100 Subject: [PATCH 06/27] shm: add shm_can_scroll() --- shm.c | 7 +++++++ shm.h | 1 + 2 files changed, 8 insertions(+) diff --git a/shm.c b/shm.c index b9cc325c..59dcb2ac 100644 --- a/shm.c +++ b/shm.c @@ -280,9 +280,16 @@ shm_fini(void) } } +bool +shm_can_scroll(void) +{ + return can_punch_hole; +} + bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) { + assert(can_punch_hole); assert(buf->busy); assert(buf->pix); assert(buf->wl_buf); diff --git a/shm.h b/shm.h index 2b3a9b19..6801ed93 100644 --- a/shm.h +++ b/shm.h @@ -32,6 +32,7 @@ struct buffer *shm_get_buffer( struct wl_shm *shm, int width, int height, unsigned long cookie); void shm_fini(void); +bool shm_can_scroll(void); bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows); void shm_purge(struct wl_shm *shm, unsigned long cookie); From 7fd691644633caeb04e0816b6743c99ffddce001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:12:19 +0100 Subject: [PATCH 07/27] render: margins: regression: fix incorrect margins --- render.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/render.c b/render.c index 8cefc833..9da5b49c 100644 --- a/render.c +++ b/render.c @@ -509,15 +509,26 @@ render_margin(struct terminal *term, struct buffer *buf, int start_line, int end pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix, &bg, 2, (pixman_rectangle16_t[]){ - {0, term->margins.top + start_line * term->cell_height, - term->margins.left, (end_line - start_line) * term->cell_height}, /* Left */ - {rmargin, term->margins.top, term->margins.right, (end_line - start_line) * term->cell_height}, /* Right */ - }); + /* 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, term->margins.left, (end_line - start_line) * term->cell_height); + 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, term->margins.right, (end_line - start_line) * term->cell_height); + term->window->surface, + rmargin, term->margins.top + start_line * term->cell_height, + term->margins.right, line_count * term->cell_height); } static void From 9166be8aed4d9ee852f20fa5c129425cc8d84fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:14:30 +0100 Subject: [PATCH 08/27] render: margins: caller explicitly asks for top/bottom margins --- render.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/render.c b/render.c index 9da5b49c..2c0cbc47 100644 --- a/render.c +++ b/render.c @@ -479,18 +479,20 @@ draw_cursor: } static void -render_margin(struct terminal *term, struct buffer *buf, int start_line, int end_line) +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 */ - int rmargin = term->width - term->margins.right; - int bmargin = term->height - term->margins.bottom; + 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 (start_line == 0) { + if (top) { pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix, &bg, 1, &(pixman_rectangle16_t){0, 0, term->width, term->margins.top}); @@ -498,7 +500,7 @@ render_margin(struct terminal *term, struct buffer *buf, int start_line, int end term->window->surface, 0, 0, term->width, term->margins.top); } - if (end_line == term->rows) { + if (bottom) { pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix, &bg, 1, &(pixman_rectangle16_t){0, bmargin, term->width, term->margins.bottom}); @@ -638,7 +640,8 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, /* Restore margins */ render_margin( - term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows); + term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows, + true, true); } /* Fallback for when we either cannot do SHM scrolling, or it failed */ @@ -1261,7 +1264,7 @@ grid_render(struct terminal *term) } else { - render_margin(term, buf, 0, term->rows); + render_margin(term, buf, 0, term->rows, true, true); term_damage_view(term); } From 7958f8b5efcadbb00f7a417f0c24c0c682b7a9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:15:04 +0100 Subject: [PATCH 09/27] render: scroll: move comment to where it belongs --- render.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/render.c b/render.c index 2c0cbc47..34777c12 100644 --- a/render.c +++ b/render.c @@ -599,18 +599,6 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, did_shm_scroll = shm_scroll( term->wl->shm, buf, dmg->scroll.lines * term->cell_height); - /* - * When SHM scrolling succeeded, the scrolled in area is made - * up of newly allocated, zero-initialized memory. Thus we'll - * need to both copy the bottom scrolling region, and - * re-render the window margins. - * - * This is different from when we scroll with a simple - * memmove, since in that case, the scrolling region and - * margins are *copied*, and thus the original region+margin - * remains in place. - */ - if (!did_shm_scroll) LOG_DBG("fast scroll failed"); } else { @@ -620,6 +608,16 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, dmb->scroll.region.start, scroll_region_lines, term->rows); } + /* + * When SHM scrolling succeeded, the scrolled in area is made up + * of newly allocated, zero-initialized memory. Thus we'll need to + * both copy the bottom scrolling region, and re-render the window + * margins. + * + * This is different from when we scroll with a simple memmove, + * since in that case, the scrolling region and margins are + * *copied*, and thus the original region+margin remains in place. + */ if (did_shm_scroll) { /* Restore bottom scrolling region */ From 75180cea37e84167c1b9e125c0d7b7a7ef778ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:15:53 +0100 Subject: [PATCH 10/27] render: scroll: take margins into account when restoring bottom scroll region --- render.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/render.c b/render.c index 34777c12..2b8953b1 100644 --- a/render.c +++ b/render.c @@ -624,16 +624,16 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, if (dmg->scroll.region.end < term->rows) { int src = dmg->scroll.region.end - dmg->scroll.lines; int dst = dmg->scroll.region.end; - size_t amount = max(0, term->rows - dmg->scroll.region.end); + size_t amount = term->rows - dmg->scroll.region.end; LOG_DBG("memmoving %zu lines of scroll region", amount); - assert(src >= 0); uint8_t *raw = buf->mmapped; - memmove(raw + dst * term->cell_height * buf->stride, - raw + src * term->cell_height * buf->stride, - amount * term->cell_height * buf->stride); + memmove( + raw + (term->margins.top + dst * term->cell_height) * buf->stride, + raw + (term->margins.top + src * term->cell_height) * buf->stride, + amount * term->cell_height * buf->stride); } /* Restore margins */ From 00129b1935355b9b05524ec901f0ff36e446323d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:16:19 +0100 Subject: [PATCH 11/27] render: scroll: use shm scrolling even though we have a top region --- render.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/render.c b/render.c index 2b8953b1..89b8fd0e 100644 --- a/render.c +++ b/render.c @@ -551,6 +551,7 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, gettimeofday(&start_time, NULL); #endif + uint8_t *raw = buf->mmapped; 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; @@ -587,15 +588,26 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, * SHM. Otherwise use memmove. */ bool try_shm_scroll = - dmg->scroll.region.start == 0 && - dmg->scroll.lines + (term->rows - dmg->scroll.region.end) < term->rows / 2; + shm_can_scroll() && (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; + /* TODO: this could easily use up all stack */ + uint8_t top_region[dmg->scroll.region.start * term->cell_height * buf->stride]; + if (try_shm_scroll) { + if (dmg->scroll.region.start > 0) { + /* Store a copy of the top region - we need to restore it after scrolling */ + memcpy(top_region, + (uint8_t *)buf->mmapped + term->margins.top * buf->stride, + sizeof(top_region)); + } + did_shm_scroll = shm_scroll( term->wl->shm, buf, dmg->scroll.lines * term->cell_height); @@ -620,6 +632,12 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, */ if (did_shm_scroll) { + /* Mmap changed - update buffer pointer */ + raw = buf->mmapped; + + /* Restore top scrolling region */ + memcpy(raw + term->margins.top * buf->stride, top_region, sizeof(top_region)); + /* Restore bottom scrolling region */ if (dmg->scroll.region.end < term->rows) { int src = dmg->scroll.region.end - dmg->scroll.lines; @@ -629,7 +647,6 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, LOG_DBG("memmoving %zu lines of scroll region", amount); assert(src >= 0); - uint8_t *raw = buf->mmapped; memmove( raw + (term->margins.top + dst * term->cell_height) * buf->stride, raw + (term->margins.top + src * term->cell_height) * buf->stride, From 0de3701984ff3b09a804d8369dafc3cd5baefdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 20:45:27 +0100 Subject: [PATCH 12/27] shm: scroll: move top/bottom region handling from renderer into shm This allows us to restore the regions without copying the contents to temporary memory. --- render.c | 63 ++++++++++---------------------------------------------- shm.c | 49 +++++++++++++++++++++++++++++++++++++------ shm.h | 4 +++- 3 files changed, 57 insertions(+), 59 deletions(-) diff --git a/render.c b/render.c index 89b8fd0e..2eab275e 100644 --- a/render.c +++ b/render.c @@ -551,7 +551,6 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, gettimeofday(&start_time, NULL); #endif - uint8_t *raw = buf->mmapped; 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; @@ -563,12 +562,13 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, * memmove, while scrolling a *small* number of lines is faster * with SHM scrolling. * - * However, since we need to restore the bottom scrolling region - * when SHM scrolling, we also need to take this into account. + * However, since we need to restore the scrolling regions when + * SHM scrolling, we also need to take this into account. * - * Finally, restoring the window margins is a *huge* performance - * hit when scrolling a large number of lines (in addition to the - * sloweness of SHM scrolling as method). + * 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. @@ -597,19 +597,11 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, //try_shm_scroll = false; //try_shm_scroll = true; - /* TODO: this could easily use up all stack */ - uint8_t top_region[dmg->scroll.region.start * term->cell_height * buf->stride]; - if (try_shm_scroll) { - if (dmg->scroll.region.start > 0) { - /* Store a copy of the top region - we need to restore it after scrolling */ - memcpy(top_region, - (uint8_t *)buf->mmapped + term->margins.top * buf->stride, - sizeof(top_region)); - } - did_shm_scroll = shm_scroll( - term->wl->shm, buf, dmg->scroll.lines * term->cell_height); + 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) LOG_DBG("fast scroll failed"); @@ -620,47 +612,14 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, dmb->scroll.region.start, scroll_region_lines, term->rows); } - /* - * When SHM scrolling succeeded, the scrolled in area is made up - * of newly allocated, zero-initialized memory. Thus we'll need to - * both copy the bottom scrolling region, and re-render the window - * margins. - * - * This is different from when we scroll with a simple memmove, - * since in that case, the scrolling region and margins are - * *copied*, and thus the original region+margin remains in place. - */ if (did_shm_scroll) { - /* Mmap changed - update buffer pointer */ - raw = buf->mmapped; - - /* Restore top scrolling region */ - memcpy(raw + term->margins.top * buf->stride, top_region, sizeof(top_region)); - - /* Restore bottom scrolling region */ - if (dmg->scroll.region.end < term->rows) { - int src = dmg->scroll.region.end - dmg->scroll.lines; - int dst = dmg->scroll.region.end; - size_t amount = term->rows - dmg->scroll.region.end; - - LOG_DBG("memmoving %zu lines of scroll region", amount); - assert(src >= 0); - - memmove( - raw + (term->margins.top + dst * term->cell_height) * buf->stride, - raw + (term->margins.top + src * term->cell_height) * buf->stride, - amount * term->cell_height * buf->stride); - } - /* Restore margins */ render_margin( term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows, true, true); - } - - /* Fallback for when we either cannot do SHM scrolling, or it failed */ - if (!did_shm_scroll) { + } 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, diff --git a/shm.c b/shm.c index 59dcb2ac..d05210a2 100644 --- a/shm.c +++ b/shm.c @@ -287,7 +287,9 @@ shm_can_scroll(void) } bool -shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) +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) { assert(can_punch_hole); assert(buf->busy); @@ -323,9 +325,25 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) struct timeval tot; timersub(&time1, &time0, &tot); - LOG_INFO("fallocate: %lds %ldus", tot.tv_sec, tot.tv_usec); + LOG_INFO("ftruncate: %lds %ldus", tot.tv_sec, tot.tv_usec); + + 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) */ pixman_image_unref(buf->pix); wl_buffer_destroy(buf->wl_buf); @@ -341,13 +359,32 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows) } #if TIME_SCROLL - struct timeval time2; - gettimeofday(&time2, NULL); - timersub(&time2, &time1, &tot); + struct timeval time3; + gettimeofday(&time3, NULL); + timersub(&time3, &time2, &tot); LOG_INFO("PUNCH HOLE: %lds %ldus", tot.tv_sec, tot.tv_usec); #endif - return instantiate_offset(shm, buf, new_offset); + + bool ret = instantiate_offset(shm, buf, new_offset); + + 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 time4; + gettimeofday(&time4, NULL); + + timersub(&time4, &time3, &tot); + LOG_INFO("memmove (bottom region): %lds %ldus", tot.tv_sec, tot.tv_usec); +#endif + } + + return ret; } void diff --git a/shm.h b/shm.h index 6801ed93..6637182c 100644 --- a/shm.h +++ b/shm.h @@ -33,7 +33,9 @@ struct buffer *shm_get_buffer( void shm_fini(void); bool shm_can_scroll(void); -bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows); +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); From 759fd572e972eea24ccc864b22203b1b71030e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 23 Mar 2020 21:14:51 +0100 Subject: [PATCH 13/27] shm: shm_scroll(): initial implementation of reverse scrolling Implemented by truncating the file size and moving the offset backwards. This means we can only reverse scroll when we've previously scrolled forward. TODO: figure out if we can somehow do fast reverse scrolling even though offset is 0 (or well, less than required for scrolling). --- render.c | 71 ++++++++++++++++++++++++++++----------- shm.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/render.c b/render.c index 2eab275e..c4ea9bc6 100644 --- a/render.c +++ b/render.c @@ -602,14 +602,6 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, 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) - LOG_DBG("fast scroll failed"); - } else { - LOG_DBG( - "skipping SHM scroll " - "(region.start=%d, bottom-region-lines=%d, term-lines=%d)", - dmb->scroll.region.start, scroll_region_lines, term->rows); } if (did_shm_scroll) { @@ -647,26 +639,65 @@ 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() && (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 diff --git a/shm.c b/shm.c index d05210a2..1f2bd1a6 100644 --- a/shm.c +++ b/shm.c @@ -64,8 +64,6 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, size_t new_offset) assert(buf->wl_buf == NULL); assert(buf->pix == NULL); - assert(new_offset >= buf->offset); - static size_t page_size = 0; if (page_size == 0) { page_size = sysconf(_SC_PAGE_SIZE); @@ -286,10 +284,10 @@ shm_can_scroll(void) return can_punch_hole; } -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) +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); @@ -365,7 +363,6 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows, LOG_INFO("PUNCH HOLE: %lds %ldus", tot.tv_sec, tot.tv_usec); #endif - bool ret = instantiate_offset(shm, buf, new_offset); if (ret && bottom_keep_rows > 0) { @@ -387,7 +384,94 @@ shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows, return ret; } -void +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); + + size_t diff = rows * buf->stride; + if (diff > buf->offset) + return false; + + const size_t new_offset = buf->offset - rows * buf->stride; + assert(new_offset < buf->offset); + +#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) */ + pixman_image_unref(buf->pix); + wl_buffer_destroy(buf->wl_buf); + munmap(buf->real_mmapped, buf->mmap_size); + + buf->pix = NULL; + buf->wl_buf = NULL; + buf->real_mmapped = buf->mmapped = NULL; + + if (ftruncate(buf->fd, new_offset + buf->size) < 0) { + abort(); + } + +#if TIME_SCROLL + struct timeval time2; + gettimeofday(&time2, NULL); + timersub(&time2, &time1, &tot); + LOG_INFO("ftruncate: %lds %ldus", tot.tv_sec, tot.tv_usec); +#endif + + bool ret = instantiate_offset(shm, buf, new_offset); + + 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 time3; + gettimeofday(&time3, NULL); + timersub(&time3, &time2, &tot); + LOG_INFO("memmove (top region): %lds %ldus", tot.tv_sec, tot.tv_usec); +#endif + } + + return ret; +} + +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) +{ + 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); From b42709525c136a68f08ca7c2bb9dc98e94257ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 17:44:17 +0100 Subject: [PATCH 14/27] quirks: add KDE quirk: surface must be damaged *after* being attached KDE resets all surface damage when a buffer is attached. Add a quirk that adds a full-buffer damage to the surface. This quirk is intended to be called after attaching a buffer to a surface. --- quirks.c | 37 ++++++++++++++++++++++++------------- quirks.h | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/quirks.c b/quirks.c index 05d2dd5f..6685d614 100644 --- a/quirks.c +++ b/quirks.c @@ -2,6 +2,7 @@ #include #include +#include #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); +} diff --git a/quirks.h b/quirks.h index 7b6ab4f8..583c0c9f 100644 --- a/quirks.h +++ b/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); From 9a3e97afa7ef8222604c61107e0c376ac03fd666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 17:45:16 +0100 Subject: [PATCH 15/27] render: clear scroll damage list when we force-refresh the entire window --- render.c | 1 + 1 file changed, 1 insertion(+) diff --git a/render.c b/render.c index e2e45430..3eaf1d78 100644 --- a/render.c +++ b/render.c @@ -1269,6 +1269,7 @@ grid_render(struct terminal *term) } else { + tll_free(term->grid->scroll_damage); render_margin(term, buf, 0, term->rows, true, true); term_damage_view(term); } From b08238a1a1f5a84bbb587e754da3db5beafe3a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 17:45:38 +0100 Subject: [PATCH 16/27] render: attach buffer just before commit --- render.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/render.c b/render.c index 3eaf1d78..36b4b103 100644 --- a/render.c +++ b/render.c @@ -1330,8 +1330,8 @@ 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); - wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); if (term->render.workers.count > 0) { @@ -1485,6 +1485,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 From dc393bd5d766dd10e680d08512426704997770e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 17:45:45 +0100 Subject: [PATCH 17/27] render: bug: clear clip region before tinting the window --- render.c | 1 + 1 file changed, 1 insertion(+) diff --git a/render.c b/render.c index 36b4b103..9109c29d 100644 --- a/render.c +++ b/render.c @@ -1440,6 +1440,7 @@ grid_render(struct terminal *term) 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, buf->pix, &(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}, From 5c5f1d096c3a2a6cb8fb3dc5cb3efad5782c1d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 17:46:48 +0100 Subject: [PATCH 18/27] shm: scroll: implement offset wrap-around * Impose a maximum memfd size limit. In theory, this can be 2GB (wl_shm_create_pool() is the limiting factor - its size argument is an int32_t). For now, use 256MB. This is mainly to reduce the amount of virtual address space used by the compositor, which keeps at least one mmapping (of the entire memfd) around. One mmapping *per terminal window* that is. Given that we have 128TB with 48-bit virtual addresses, we could probably bump this to 2GB without any issues. However, 256MB should be enough. TODO: check how much we typically move the offset when scrolling in a fullscreen window on a 4K monitor. 256MB may turn out to be too small. On 32-bit shm_scroll() is completely disabled. There simply isn't enough address space. * Wrapping is done by moving the offset to "the other end" of the memfd, and copying the buffer contents to the new, wrapped offset. The "normal" scrolling code then does the actual scrolling. This means we'll re-instantiate all objects twice when wrapping. --- shm.c | 193 +++++++++++++++++++++++++++++++++++++++++++++------------- shm.h | 3 +- 2 files changed, 154 insertions(+), 42 deletions(-) diff --git a/shm.c b/shm.c index 1f2bd1a6..a7ce6850 100644 --- a/shm.c +++ b/shm.c @@ -23,13 +23,33 @@ #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. + */ +static const off_t max_pool_size = 256 * 1024 * 1024; + static tll(struct buffer) buffers; static bool can_punch_hole = false; static bool can_punch_hole_initialized = false; static void -buffer_destroy(struct buffer *buf) +buffer_destroy_dont_close(struct buffer *buf) { if (buf->pix != NULL) pixman_image_unref(buf->pix); @@ -37,8 +57,20 @@ buffer_destroy(struct buffer *buf) wl_buffer_destroy(buf->wl_buf); if (buf->real_mmapped != MAP_FAILED) munmap(buf->real_mmapped, buf->mmap_size); + + buf->pix = NULL; + buf->wl_buf = NULL; + buf->real_mmapped = NULL; + buf->mmapped = NULL; +} + +static void +buffer_destroy(struct buffer *buf) +{ + buffer_destroy_dont_close(buf); if (buf->fd >= 0) close(buf->fd); + buf->fd = -1; } static void @@ -55,24 +87,30 @@ 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, size_t new_offset) +instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset) { assert(buf->fd >= 0); assert(buf->mmapped == NULL); assert(buf->real_mmapped == NULL); assert(buf->wl_buf == NULL); assert(buf->pix == NULL); - - static size_t page_size = 0; - if (page_size == 0) { - page_size = sysconf(_SC_PAGE_SIZE); - if (page_size < 0) { - LOG_ERRNO("failed to get page size"); - page_size = 4096; - } - } - assert(page_size > 0); + assert(new_offset + buf->size <= max_pool_size); void *real_mmapped = MAP_FAILED; void *mmapped = MAP_FAILED; @@ -81,8 +119,8 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, size_t new_offset) pixman_image_t *pix = NULL; /* mmap offset must be page aligned */ - size_t aligned_offset = new_offset & ~(page_size - 1); - size_t page_offset = new_offset & (page_size - 1); + off_t aligned_offset = new_offset & ~(page_size() - 1); + size_t page_offset = new_offset & (page_size() - 1); size_t mmap_size = buf->size + page_offset; assert(aligned_offset <= new_offset); @@ -222,7 +260,17 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) goto err; } - if (ftruncate(pool_fd, size) == -1) { + /* + * If we can figure our if we can punch holes *before* this, we + * could set the initial offset to somewhere in the middle of the + * avaiable address space. This would allow both backward and + * forward scrolling without immediately needing to wrap. + */ + //off_t initial_offset = (max_pool_size / 4) & ~(page_size() - 1); + off_t initial_offset = 0; + off_t memfd_size = initial_offset + size; + + if (ftruncate(pool_fd, memfd_size) == -1) { LOG_ERRNO("failed to truncate SHM pool"); goto err; } @@ -256,7 +304,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) ); struct buffer *ret = &tll_back(buffers); - if (!instantiate_offset(shm, ret, 0)) + if (!instantiate_offset(shm, ret, initial_offset)) goto err; return ret; @@ -281,7 +329,36 @@ shm_fini(void) bool shm_can_scroll(void) { +#if defined(__i386__) + /* Not enough virtual address space in 32-bit */ + return false; +#else return can_punch_hole; +#endif +} + +static bool +wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset) +{ + off_t aligned_offset = new_offset & ~(page_size() - 1); + size_t page_offset = new_offset & (page_size() - 1); + size_t mmap_size = buf->size + page_offset; + + assert(aligned_offset <= new_offset); + assert(mmap_size >= buf->size); + + uint8_t *m = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, buf->fd, aligned_offset); + if (m == MAP_FAILED) { + LOG_ERRNO("failed to mmap"); + return false; + } + + memcpy(m + page_offset, buf->mmapped, buf->size); + munmap(m, mmap_size); + + /* Re-instantiate pixman+wl_buffer+raw pointersw */ + buffer_destroy_dont_close(buf); + return instantiate_offset(shm, buf, new_offset); } static bool @@ -303,7 +380,22 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, assert(rows > 0); assert(rows * buf->stride < buf->size); - const size_t new_offset = buf->offset + rows * buf->stride; + + if (buf->offset + rows * buf->stride + buf->size > max_pool_size) { + LOG_INFO("memfd offset wrap around"); + assert(buf->offset > buf->size); + + /* + * Wrap around by moving the offset to the beginning of the + * memfd. The ftruncate() we do below takes care of trimming + * down the size. + */ + if (!wrap_buffer(shm, buf, 0)) + goto err; + } + + off_t new_offset = buf->offset + rows * buf->stride; + assert(new_offset + buf->size <= max_pool_size); #if TIME_SCROLL struct timeval time0; @@ -312,9 +404,9 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, /* Increase file size */ if (ftruncate(buf->fd, new_offset + buf->size) < 0) { - LOG_ERRNO("failed increase memfd size from %zu -> %zu", + LOG_ERRNO("failed to resize memfd from %zu -> %zu", buf->offset + buf->size, new_offset + buf->size); - return false; + goto err; } #if TIME_SCROLL @@ -343,19 +435,17 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, } /* Destroy old objects (they point to the old offset) */ - pixman_image_unref(buf->pix); - wl_buffer_destroy(buf->wl_buf); - munmap(buf->real_mmapped, buf->mmap_size); - - buf->pix = NULL; - buf->wl_buf = NULL; - buf->real_mmapped = buf->mmapped = NULL; + buffer_destroy_dont_close(buf); /* Free unused memory */ - if (fallocate(buf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, new_offset) < 0) { - abort(); + if (new_offset > 0 && + fallocate(buf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, new_offset) < 0) + { + LOG_ERRNO("fallocate(FALLOC_FL_PUNCH_HOLE, 0, %lu) failed", new_offset); + goto err; } + #if TIME_SCROLL struct timeval time3; gettimeofday(&time3, NULL); @@ -363,6 +453,7 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, 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 (ret && bottom_keep_rows > 0) { @@ -382,6 +473,10 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, } return ret; + +err: + abort(); + return false; } static bool @@ -391,12 +486,27 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, { assert(rows > 0); - size_t diff = rows * buf->stride; - if (diff > buf->offset) - return false; + off_t diff = rows * buf->stride; + if (diff > buf->offset) { + LOG_INFO("memfd offset reverse wrap-around"); - const size_t new_offset = buf->offset - rows * buf->stride; - assert(new_offset < buf->offset); + /* + * Wrap around by resizing the memfd and moving the offset to + * the end of the file, taking care the new offset is aligned. + */ + + if (ftruncate(buf->fd, max_pool_size) < 0) { + LOG_ERRNO("failed to resize memfd from %zu -> %zu", + buf->offset + buf->size, max_pool_size - buf->size); + goto err; + } + + 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 <= max_pool_size); #if TIME_SCROLL struct timeval time0; @@ -421,16 +531,12 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, } /* Destroy old objects (they point to the old offset) */ - pixman_image_unref(buf->pix); - wl_buffer_destroy(buf->wl_buf); - munmap(buf->real_mmapped, buf->mmap_size); - - buf->pix = NULL; - buf->wl_buf = NULL; - buf->real_mmapped = buf->mmapped = NULL; + buffer_destroy_dont_close(buf); if (ftruncate(buf->fd, new_offset + buf->size) < 0) { - abort(); + LOG_ERRNO("failed to resize memfd from %zu -> %zu", + buf->offset + buf->size, new_offset + buf->size); + goto err; } #if TIME_SCROLL @@ -440,6 +546,7 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, LOG_INFO("ftruncate: %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 (ret && top_keep_rows > 0) { @@ -458,6 +565,10 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, } return ret; + +err: + abort(); + return false; } bool diff --git a/shm.h b/shm.h index 6637182c..d67b3567 100644 --- a/shm.h +++ b/shm.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -24,7 +25,7 @@ struct buffer { int fd; /* memfd */ void *real_mmapped; /* Address returned from mmap */ size_t mmap_size; /* Size of mmap (>= size) */ - size_t offset; /* Offset into memfd where data begins */ + off_t offset; /* Offset into memfd where data begins */ bool purge; /* True if this buffer should be destroyed */ }; From b46ad6a50a121a8b8d0a25c8c62cda8cd41a2255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Mar 2020 21:04:30 +0100 Subject: [PATCH 19/27] render: explain _why_ we set a clip region --- render.c | 1 + 1 file changed, 1 insertion(+) diff --git a/render.c b/render.c index 9109c29d..2b86fe69 100644 --- a/render.c +++ b/render.c @@ -1279,6 +1279,7 @@ 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, From 9bbbd26c7a2bad131b093cfd559a68fb59613af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 18:23:55 +0100 Subject: [PATCH 20/27] render: pace title updates Synchronize window title updates with window rendering. --- render.c | 44 ++++++++++++++++++++++++++++++-------------- render.h | 2 +- terminal.c | 2 +- terminal.h | 2 ++ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/render.c b/render.c index 2b86fe69..4238a4b8 100644 --- a/render.c +++ b/render.c @@ -1602,6 +1602,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) { @@ -1614,10 +1632,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); @@ -1625,6 +1645,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); @@ -1957,10 +1980,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) { @@ -1968,6 +1993,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) @@ -1977,6 +2004,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; } } @@ -1991,21 +2019,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 diff --git a/render.h b/render.h index 99246f17..0c03f125 100644 --- a/render.h +++ b/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 { diff --git a/terminal.c b/terminal.c index 36f0e42b..0418b688 100644 --- a/terminal.c +++ b/terminal.c @@ -1943,7 +1943,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 diff --git a/terminal.h b/terminal.h index 3685c0d7..9b97cf4e 100644 --- a/terminal.h +++ b/terminal.h @@ -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?) */ From 1891489cd6c873f2522088a9b43fa45f5a609e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 18:24:58 +0100 Subject: [PATCH 21/27] app synchronized updates: set is_armed=false when enabling --- terminal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/terminal.c b/terminal.c index 0418b688..9352e5df 100644 --- a/terminal.c +++ b/terminal.c @@ -2052,6 +2052,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 From 03319560f5c7c908b1c1d229b3ba53ed1ac6b692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 18:26:58 +0100 Subject: [PATCH 22/27] shm: scroll: keep shm pool around, and fix its size at max allowed This lessens the burden on (primarily) the compositor, since we no longer tear down and re-create the SHM pool when scrolling. The SHM pool is setup once, and its size is fixed at the maximum allowed (512MB for now, 2GB would be possible). This also allows us to mmap() the memfd once. The exposed raw pointer is simply an offset from the memfd mmapping. Note that this means e.g. rouge rendering code will be able to write outside the buffer. Finally, only do this if the caller explicitly wants to enable scrolling. The memfd of other buffers are sized to the requested size. --- render.c | 24 +++--- shm.c | 253 ++++++++++++++++++++++++++++--------------------------- shm.h | 10 ++- 3 files changed, 149 insertions(+), 138 deletions(-) diff --git a/render.c b/render.c index 4238a4b8..f77b7b3d 100644 --- a/render.c +++ b/render.c @@ -588,9 +588,10 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, * SHM. Otherwise use memmove. */ bool try_shm_scroll = - shm_can_scroll() && (dmg->scroll.lines + - dmg->scroll.region.start + - (term->rows - dmg->scroll.region.end)) < term->rows / 2; + 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; @@ -657,9 +658,10 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, int dst_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height; bool try_shm_scroll = - shm_can_scroll() && (dmg->scroll.lines + - dmg->scroll.region.start + - (term->rows - dmg->scroll.region.end)) < term->rows / 2; + 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; @@ -912,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; @@ -943,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); @@ -1112,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; @@ -1239,7 +1241,7 @@ 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); pixman_image_set_clip_region(buf->pix, NULL); @@ -1526,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( diff --git a/shm.c b/shm.c index a7ce6850..e4772936 100644 --- a/shm.c +++ b/shm.c @@ -1,9 +1,11 @@ #include "shm.h" -#include +#include #include #include #include +#include +#include #include #include @@ -41,7 +43,8 @@ * On 32-bit the available address space is too small and SHM * scrolling is disabled. */ -static const off_t max_pool_size = 256 * 1024 * 1024; +static const off_t max_pool_size = 512 * 1024 * 1024; +//static const off_t max_pool_size = INT32_MAX; static tll(struct buffer) buffers; @@ -55,12 +58,9 @@ buffer_destroy_dont_close(struct buffer *buf) pixman_image_unref(buf->pix); if (buf->wl_buf != NULL) wl_buffer_destroy(buf->wl_buf); - if (buf->real_mmapped != MAP_FAILED) - munmap(buf->real_mmapped, buf->mmap_size); buf->pix = NULL; buf->wl_buf = NULL; - buf->real_mmapped = NULL; buf->mmapped = NULL; } @@ -68,8 +68,15 @@ static void buffer_destroy(struct buffer *buf) { 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; } @@ -107,51 +114,23 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset) { assert(buf->fd >= 0); assert(buf->mmapped == NULL); - assert(buf->real_mmapped == NULL); assert(buf->wl_buf == NULL); assert(buf->pix == NULL); assert(new_offset + buf->size <= max_pool_size); - void *real_mmapped = MAP_FAILED; void *mmapped = MAP_FAILED; - struct wl_shm_pool *pool = NULL; struct wl_buffer *wl_buf = NULL; pixman_image_t *pix = NULL; - /* mmap offset must be page aligned */ - off_t aligned_offset = new_offset & ~(page_size() - 1); - size_t page_offset = new_offset & (page_size() - 1); - size_t mmap_size = buf->size + page_offset; - - assert(aligned_offset <= new_offset); - assert(mmap_size >= buf->size); - - LOG_DBG("size=%zx, offset=%zx, size-aligned=%zx, offset-aligned=%zx", - buf->size, buf->offset, mmap_size, aligned_offset); - - real_mmapped = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, buf->fd, aligned_offset); - if (real_mmapped == MAP_FAILED) { - LOG_ERRNO("failed to mmap SHM backing memory file"); - goto err; - } - mmapped = real_mmapped + page_offset; - - pool = wl_shm_create_pool(shm, buf->fd, new_offset + buf->size); - if (pool == NULL) { - LOG_ERR("failed to create SHM pool"); - goto err; - } + mmapped = (uint8_t *)buf->real_mmapped + new_offset; wl_buf = wl_shm_pool_create_buffer( - pool, new_offset, buf->width, buf->height, buf->stride, WL_SHM_FORMAT_ARGB8888); + 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; } - /* We use the entire pool for our single buffer */ - wl_shm_pool_destroy(pool); pool = NULL; - /* 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); @@ -161,9 +140,7 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset) } buf->offset = new_offset; - buf->real_mmapped = real_mmapped; buf->mmapped = mmapped; - buf->mmap_size = mmap_size; buf->wl_buf = wl_buf; buf->pix = pix; @@ -175,17 +152,13 @@ err: pixman_image_unref(pix); if (wl_buf != NULL) wl_buffer_destroy(wl_buf); - if (pool != NULL) - wl_shm_pool_destroy(pool); - if (real_mmapped != MAP_FAILED) - munmap(real_mmapped, mmap_size); 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) { @@ -251,27 +224,28 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) 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; + 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; } - /* - * If we can figure our if we can punch holes *before* this, we - * could set the initial offset to somewhere in the middle of the - * avaiable address space. This would allow both backward and - * forward scrolling without immediately needing to wrap. - */ - //off_t initial_offset = (max_pool_size / 4) & ~(page_size() - 1); +#if defined(__i386__) off_t initial_offset = 0; - off_t memfd_size = initial_offset + size; + off_t memfd_size = size; +#else + off_t initial_offset = scrollable ? (max_pool_size / 4) & ~(page_size() - 1) : 0; + off_t memfd_size = scrollable ? max_pool_size : size; +#endif if (ftruncate(pool_fd, memfd_size) == -1) { - LOG_ERRNO("failed to truncate SHM pool"); + LOG_ERRNO("failed to set size of SHM backing memory file"); goto err; } @@ -287,6 +261,28 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) } } + if (scrollable && !can_punch_hole) { + initial_offset = 0; + memfd_size = size; + ftruncate(pool_fd, memfd_size); + scrollable = false; + } + + 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, memfd_size); + if (pool == NULL) { + LOG_ERR("failed to create SHM pool"); + goto err; + } + /* Push to list of available buffers, but marked as 'busy' */ tll_push_back( buffers, @@ -298,7 +294,10 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) .busy = true, .size = size, .fd = pool_fd, - .mmap_size = size, + .pool = pool, + .scrollable = scrollable, + .real_mmapped = real_mmapped, + .mmap_size = memfd_size, .offset = 0} ) ); @@ -309,6 +308,10 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie) return ret; err: + 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); @@ -327,34 +330,45 @@ shm_fini(void) } bool -shm_can_scroll(void) +shm_can_scroll(const struct buffer *buf) { #if defined(__i386__) /* Not enough virtual address space in 32-bit */ return false; #else - return can_punch_hole; + return can_punch_hole && buf->scrollable; #endif } static bool wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset) { - off_t aligned_offset = new_offset & ~(page_size() - 1); - size_t page_offset = new_offset & (page_size() - 1); - size_t mmap_size = buf->size + page_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); - assert(aligned_offset <= new_offset); - assert(mmap_size >= buf->size); + memcpy((uint8_t *)buf->real_mmapped + new_offset, buf->mmapped, buf->size); - uint8_t *m = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_UNINITIALIZED, buf->fd, aligned_offset); - if (m == MAP_FAILED) { - LOG_ERRNO("failed to mmap"); - return false; + 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; } - memcpy(m + page_offset, buf->mmapped, buf->size); - munmap(m, mmap_size); + 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); @@ -370,7 +384,6 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, assert(buf->busy); assert(buf->pix); assert(buf->wl_buf); - assert(buf->real_mmapped); assert(buf->fd >= 0); if (!can_punch_hole) @@ -378,45 +391,24 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride); + const off_t diff = rows * buf->stride; assert(rows > 0); - assert(rows * buf->stride < buf->size); + assert(diff < buf->size); - if (buf->offset + rows * buf->stride + buf->size > max_pool_size) { - LOG_INFO("memfd offset wrap around"); - assert(buf->offset > buf->size); - - /* - * Wrap around by moving the offset to the beginning of the - * memfd. The ftruncate() we do below takes care of trimming - * down the 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 + rows * buf->stride; + 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 time0; - gettimeofday(&time0, NULL); -#endif - - /* Increase file size */ - if (ftruncate(buf->fd, new_offset + buf->size) < 0) { - LOG_ERRNO("failed to resize memfd from %zu -> %zu", - buf->offset + buf->size, new_offset + buf->size); - goto err; - } - #if TIME_SCROLL struct timeval time1; gettimeofday(&time1, NULL); - struct timeval tot; - timersub(&time1, &time0, &tot); - LOG_INFO("ftruncate: %lds %ldus", tot.tv_sec, tot.tv_usec); - struct timeval time2 = time1; #endif @@ -437,15 +429,19 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, /* Destroy old objects (they point to the old offset) */ buffer_destroy_dont_close(buf); - /* Free unused memory */ - if (new_offset > 0 && - fallocate(buf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, new_offset) < 0) + /* 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("fallocate(FALLOC_FL_PUNCH_HOLE, 0, %lu) failed", new_offset); + LOG_ERRNO("failed to trim SHM backing memory file"); goto err; } - #if TIME_SCROLL struct timeval time3; gettimeofday(&time3, NULL); @@ -456,6 +452,13 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, /* 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( @@ -464,10 +467,10 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, bottom_keep_rows * buf->stride); #if TIME_SCROLL - struct timeval time4; - gettimeofday(&time4, NULL); + struct timeval time5; + gettimeofday(&time5, NULL); - timersub(&time4, &time3, &tot); + timersub(&time5, &time4, &tot); LOG_INFO("memmove (bottom region): %lds %ldus", tot.tv_sec, tot.tv_usec); #endif } @@ -486,26 +489,15 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, { assert(rows > 0); - off_t diff = rows * buf->stride; + const off_t diff = rows * buf->stride; if (diff > buf->offset) { - LOG_INFO("memfd offset reverse wrap-around"); - - /* - * Wrap around by resizing the memfd and moving the offset to - * the end of the file, taking care the new offset is aligned. - */ - - if (ftruncate(buf->fd, max_pool_size) < 0) { - LOG_ERRNO("failed to resize memfd from %zu -> %zu", - buf->offset + buf->size, max_pool_size - buf->size); - goto err; - } - + 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 @@ -533,22 +525,35 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, /* Destroy old objects (they point to the old offset) */ buffer_destroy_dont_close(buf); - if (ftruncate(buf->fd, new_offset + buf->size) < 0) { - LOG_ERRNO("failed to resize memfd from %zu -> %zu", - buf->offset + buf->size, new_offset + buf->size); + /* 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("ftruncate: %lds %ldus", tot.tv_sec, tot.tv_usec); + 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( @@ -557,9 +562,9 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows, top_keep_rows * buf->stride); #if TIME_SCROLL - struct timeval time3; - gettimeofday(&time3, NULL); - timersub(&time3, &time2, &tot); + struct timeval time4; + gettimeofday(&time4, NULL); + timersub(&time4, &time2, &tot); LOG_INFO("memmove (top region): %lds %ldus", tot.tv_sec, tot.tv_usec); #endif } diff --git a/shm.h b/shm.h index d67b3567..fe3f4b93 100644 --- a/shm.h +++ b/shm.h @@ -16,24 +16,28 @@ struct buffer { bool busy; size_t size; /* Buffer size */ - void *mmapped; /* Raw data */ + 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); -bool shm_can_scroll(void); +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); From dc42cc1d195a16683c45fc87eb2dc333087a90da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 18:30:21 +0100 Subject: [PATCH 23/27] shm: seal the memfd This both prevents accidental resizing of the memfd, and allows the Wayland server to optimze reads from the buffer - it no longer has to setup SIGBUS handlers. --- shm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shm.c b/shm.c index e4772936..dc633ae3 100644 --- a/shm.c +++ b/shm.c @@ -277,6 +277,15 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, goto err; } + /* 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"); From e9f1638750db342fbf26fc9477a970ab2e3e439d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 18:32:41 +0100 Subject: [PATCH 24/27] shm: handle ftruncate failure --- shm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shm.c b/shm.c index dc633ae3..7ac1f6c8 100644 --- a/shm.c +++ b/shm.c @@ -264,8 +264,12 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, if (scrollable && !can_punch_hole) { initial_offset = 0; memfd_size = size; - ftruncate(pool_fd, memfd_size); scrollable = false; + + if (ftruncate(pool_fd, memfd_size) < 0) { + LOG_ERRNO("failed to set size of SHM backing memory file"); + goto err; + } } real_mmapped = mmap( From 0baa249d8b59007913dbf7db8aa8d94e6c2b9d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 25 Mar 2020 20:48:02 +0100 Subject: [PATCH 25/27] shm: make max pool size user configurable (via a 'tweak' setting) --- config.c | 14 ++++++++++++++ config.h | 1 + main.c | 2 ++ shm.c | 32 +++++++++++++++++++++----------- shm.h | 1 + 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/config.c b/config.c index 80163e2e..16831248 100644 --- a/config.c +++ b/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; @@ -621,6 +622,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; @@ -890,6 +903,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, }, }; diff --git a/config.h b/config.h index 596aa368..f53a1b7b 100644 --- a/config.h +++ b/config.h @@ -77,6 +77,7 @@ struct config { struct { uint64_t delayed_render_lower_ns; uint64_t delayed_render_upper_ns; + off_t max_shm_pool_size; } tweak; }; diff --git a/main.c b/main.c index b6ca0fa4..b7dbef13 100644 --- a/main.c +++ b/main.c @@ -330,6 +330,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; diff --git a/shm.c b/shm.c index 7ac1f6c8..c2e99004 100644 --- a/shm.c +++ b/shm.c @@ -42,15 +42,23 @@ * * 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 const off_t max_pool_size = 512 * 1024 * 1024; -//static const off_t max_pool_size = INT32_MAX; +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) { @@ -80,6 +88,15 @@ buffer_destroy(struct buffer *buf) 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) { @@ -244,6 +261,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, off_t memfd_size = scrollable ? max_pool_size : size; #endif + LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, initial_offset); + if (ftruncate(pool_fd, memfd_size) == -1) { LOG_ERRNO("failed to set size of SHM backing memory file"); goto err; @@ -333,15 +352,6 @@ err: return NULL; } -void -shm_fini(void) -{ - tll_foreach(buffers, it) { - buffer_destroy(&it->item); - tll_remove(buffers, it); - } -} - bool shm_can_scroll(const struct buffer *buf) { diff --git a/shm.h b/shm.h index fe3f4b93..aff53d76 100644 --- a/shm.h +++ b/shm.h @@ -37,6 +37,7 @@ struct buffer *shm_get_buffer( 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, From c4aaba6299b092cd8221fb6e8c301fe5bfd602a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 26 Mar 2020 18:04:30 +0100 Subject: [PATCH 26/27] conf: max-shm-pool-size-mb=0 now disables SHM scrolling --- shm.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shm.c b/shm.c index c2e99004..5ae5653d 100644 --- a/shm.c +++ b/shm.c @@ -133,7 +133,6 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset) assert(buf->mmapped == NULL); assert(buf->wl_buf == NULL); assert(buf->pix == NULL); - assert(new_offset + buf->size <= max_pool_size); void *mmapped = MAP_FAILED; struct wl_buffer *wl_buf = NULL; @@ -257,8 +256,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, off_t initial_offset = 0; off_t memfd_size = size; #else - off_t initial_offset = scrollable ? (max_pool_size / 4) & ~(page_size() - 1) : 0; - off_t memfd_size = scrollable ? max_pool_size : size; + 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 LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, initial_offset); @@ -359,7 +358,7 @@ shm_can_scroll(const struct buffer *buf) /* Not enough virtual address space in 32-bit */ return false; #else - return can_punch_hole && buf->scrollable; + return can_punch_hole && max_pool_size > 0 && buf->scrollable; #endif } @@ -409,9 +408,6 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows, assert(buf->wl_buf); assert(buf->fd >= 0); - if (!can_punch_hole) - return false; - LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride); const off_t diff = rows * buf->stride; @@ -604,6 +600,9 @@ 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) From 043ee41c0d9ec09460b85aef3199926439af2210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 29 Mar 2020 11:32:38 +0200 Subject: [PATCH 27/27] changelog: mention renderer performance improvements with scrolling --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e5b9a85..5dec80cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,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