From 7633ab2c60b40bd5aa67ef391b2a0d149685742f Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 14:10:31 +0300 Subject: Optimize screen scrolling, SIMD wrap padding, and reflow loops - Batch full-screen scroll rotations, eliminating row-by-row scroll loops - Use block memmove for sub-region scrolling, eliminating heap/stack row swaps - Vectorize CELL_WRAP_PAD flag setting with AVX2/SSE2/NEON in screen.h - Replace scalar tab-stop loops with 64-bit word stamping - Optimize active row backwards scan in resize and erase mode 2 --- src/screen.c | 286 +++++++++++++++++++++++++++++++++++++++++++------------- src/screen.h | 58 ++++++++++++ src/selection.c | 6 +- 3 files changed, 283 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/screen.c b/src/screen.c index d6a408d..afd9b4a 100644 --- a/src/screen.c +++ b/src/screen.c @@ -163,10 +163,8 @@ const struct cell *term_ring_row(const struct term *t, int row) if (got < t->cols) { fill_cells(out + got, t->cols - got, TERM_COLOR_FG, TERM_COLOR_BG); int line_rows = term_hist_line_rows(&t->hist, id, t->cols); - if (seg + 1 < line_rows || (row + 1 == total && hist_is_open(&t->hist))) { - for (int x = got; x < t->cols; x++) - out[x].flags |= CELL_WRAP_PAD; - } + if (seg + 1 < line_rows || (row + 1 == total && hist_is_open(&t->hist))) + set_cells_wrap_pad(out + got, t->cols - got); } return out; } @@ -204,10 +202,8 @@ const struct cell *term_screen_view_row(const struct term *t, int y) if (got < t->cols) { fill_cells(out + got, t->cols - got, TERM_COLOR_FG, TERM_COLOR_BG); int line_rows = term_hist_line_rows(&t->hist, id, t->cols); - if (seg + 1 < line_rows || (id + 1 == hist_end_id(&t->hist) && hist_is_open(&t->hist))) { - for (int x = got; x < t->cols; x++) - out[x].flags |= CELL_WRAP_PAD; - } + if (seg + 1 < line_rows || (id + 1 == hist_end_id(&t->hist) && hist_is_open(&t->hist))) + set_cells_wrap_pad(out + got, t->cols - got); } return out; } @@ -561,10 +557,10 @@ void term_screen_erase(struct term *t, int mode) int rows = t->rows; int cols = t->cols; const struct srow *slots = t->scr.slot; - int p = t->scr.top ? t->scr.top - 1 : rows - 1; + int p = phys_row(t, rows - 1); for (int y = rows - 1; y >= 0; y--) { const struct srow *r = &slots[p]; - p = p ? p - 1 : rows - 1; + p = (p == 0) ? rows - 1 : p - 1; if (TERM_EXPECT(r->len == 0, 1)) { if (TERM_EXPECT((r->flags & RF_WRAPPED) != 0, 0)) tail_clean = false; @@ -577,8 +573,7 @@ void term_screen_erase(struct term *t, int mode) tail_clean = false; } if (last_active >= 0 && !t->alternate) { - for (int y = 0; y <= last_active; y++) - term_screen_scroll_up(t); + term_screen_scroll_region(t, 0, rows - 1, last_active + 1, false, true); if (tail_clean) { term_hist_view_reset_bottom(&t->view); return; @@ -612,6 +607,63 @@ void term_screen_erase(struct term *t, int mode) } } +static void rotate_and_clear_srows(struct term *t, struct srow *base, int height, + int count, bool down, bool def) +{ + struct srow tmp[64]; + struct srow *saved = (count <= 64) ? tmp : malloc((size_t)count * sizeof(struct srow)); + if (!saved) + return; + + if (!down) { + memcpy(saved, base, (size_t)count * sizeof(struct srow)); + memmove(base, base + count, (size_t)(height - count) * sizeof(struct srow)); + memcpy(base + height - count, saved, (size_t)count * sizeof(struct srow)); + + for (int i = 0; i < height - count; i++) + base[i].version++; + + for (int i = 0; i < count; i++) { + struct srow *dst = &base[height - count + i]; + dst->kind = ROW_CELLS; + dst->uid = ++t->next_row_uid; + dst->version = 1; + if (def) { + dst->len = 0; + dst->flags = 0; + } else { + fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); + dst->len = (uint16_t)t->cols; + dst->flags = RF_TAIL_PADDED; + } + } + } else { + memcpy(saved, base + height - count, (size_t)count * sizeof(struct srow)); + memmove(base + count, base, (size_t)(height - count) * sizeof(struct srow)); + memcpy(base, saved, (size_t)count * sizeof(struct srow)); + + for (int i = 0; i < height - count; i++) + base[count + i].version++; + + for (int i = 0; i < count; i++) { + struct srow *dst = &base[i]; + dst->kind = ROW_CELLS; + dst->uid = ++t->next_row_uid; + dst->version = 1; + if (def) { + dst->len = 0; + dst->flags = 0; + } else { + fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); + dst->len = (uint16_t)t->cols; + dst->flags = RF_TAIL_PADDED; + } + } + } + if (saved != tmp) + free(saved); +} + void term_screen_scroll_region(struct term *t, int top, int bottom, int count, bool down, bool history) { @@ -621,10 +673,93 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, if (count > height) count = height; - if (!down && history && top == 0 && bottom == t->rows - 1) { - while (count-- > 0) - term_screen_scroll_up(t); - return; + /* Whole active screen scroll: optimize using ring rotation. */ + if (top == 0 && bottom == t->rows - 1) { + struct screen *s = term_active_screen(t); + int rows = s->rows; + int orig_top = s->top; + bool def = using_default_colors(t); + + if (!down) { + bool has_hist = history && screen_has_history(t); + if (TERM_EXPECT(t->prompt_y >= 0, 0) && !t->alternate) { + if (history) { + if (t->prompt_y > 0) + t->prompt_y = t->prompt_y > count ? t->prompt_y - count : 0; + } else { + t->prompt_y -= count; + if (t->prompt_y < 0) { + t->prompt_y = -1; + t->has_prompt = false; + } + } + } + for (int i = 0; i < count; i++) { + int p = orig_top + i; + if (p >= rows) p -= rows; + struct srow *row = &s->slot[p]; + if (TERM_EXPECT(has_hist, 1)) + term_screen_push_row(t, row); + else + screen_drop_top(s, row); + if (history) { + screen_new_row(t, row); + } else { + row->kind = ROW_CELLS; + row->uid = ++t->next_row_uid; + row->version++; + if (def) { + row->len = 0; + row->flags = 0; + } else { + fill_cells(row->cells, t->cols, t->cur_fg, t->cur_bg); + row->len = (uint16_t)t->cols; + row->flags = RF_TAIL_PADDED; + } + } + } + int new_top = orig_top + count; + if (new_top >= rows) new_top -= rows; + s->top = new_top; + clamp_selection_after_scroll(t); + t->full_gen++; + return; + } else { + /* Full screen scroll down */ + if (!t->alternate && t->prompt_y >= 0) { + t->prompt_y += count; + if (t->prompt_y >= rows) { + t->prompt_y = -1; + t->has_prompt = false; + } + } + for (int i = 0; i < rows - count; i++) { + int p = orig_top + i; + if (p >= rows) p -= rows; + s->slot[p].version++; + } + for (int i = 0; i < count; i++) { + int p = orig_top + (rows - count) + i; + if (p >= rows) p -= rows; + struct srow *row = &s->slot[p]; + row->kind = ROW_CELLS; + row->uid = ++t->next_row_uid; + row->version = 1; + if (def) { + row->len = 0; + row->flags = 0; + } else { + fill_cells(row->cells, t->cols, t->cur_fg, t->cur_bg); + row->len = (uint16_t)t->cols; + row->flags = RF_TAIL_PADDED; + } + } + int new_top = orig_top - count; + if (new_top < 0) new_top += rows; + s->top = new_top; + t->full_gen++; + return; + } } if (!down && history && top == 0 && !t->alternate) { @@ -640,44 +775,28 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, bool def = using_default_colors(t); if (count == height) { - for (int y = top; y <= bottom; y++) { - struct srow *dst = screen_get_row(s, y); - dst->kind = ROW_CELLS; - dst->uid = ++t->next_row_uid; - dst->version++; - if (def) { - dst->len = 0; - dst->flags = 0; - } else { - fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); - dst->len = (uint16_t)t->cols; - dst->flags = RF_TAIL_PADDED; - } - } - } else { - struct srow tmp[256]; - struct srow *buf = (height <= 256) ? tmp : malloc((size_t)height * sizeof(struct srow)); - if (!buf) - return; - - for (int i = 0; i < height; i++) - buf[i] = *screen_get_row(s, top + i); - - for (int i = 0; i < height; i++) { - int src_i = down ? i - count : i + count; - struct srow *dst = screen_get_row(s, top + i); - if (src_i >= 0 && src_i < height) { - *dst = buf[src_i]; + int start = phys_row(t, top); + if (start + height <= s->rows) { + for (int i = 0; i < height; i++) { + struct srow *dst = &s->slot[start + i]; + dst->kind = ROW_CELLS; + dst->uid = ++t->next_row_uid; dst->version++; - } else { - /* The row takes the buffers of a row that left the region. */ - int dead_i = down ? (height - count + i) : (i - (height - count)); - dst->cells = buf[dead_i].cells; - dst->ascii = buf[dead_i].ascii; - dst->text = buf[dead_i].text; + if (def) { + dst->len = 0; + dst->flags = 0; + } else { + fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); + dst->len = (uint16_t)t->cols; + dst->flags = RF_TAIL_PADDED; + } + } + } else { + for (int y = top; y <= bottom; y++) { + struct srow *dst = screen_get_row(s, y); dst->kind = ROW_CELLS; dst->uid = ++t->next_row_uid; - dst->version = 1; + dst->version++; if (def) { dst->len = 0; dst->flags = 0; @@ -688,8 +807,27 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, } } } - if (buf != tmp) - free(buf); + } else { + int start = phys_row(t, top); + if (start + height <= s->rows) { + rotate_and_clear_srows(t, s->slot + start, height, count, down, def); + } else { + struct srow tmp[256]; + struct srow *buf = (height <= 256) ? tmp : malloc((size_t)height * sizeof(struct srow)); + if (!buf) + return; + int n1 = s->rows - start; + int n2 = height - n1; + memcpy(buf, s->slot + start, (size_t)n1 * sizeof(struct srow)); + memcpy(buf + n1, s->slot, (size_t)n2 * sizeof(struct srow)); + + rotate_and_clear_srows(t, buf, height, count, down, def); + + memcpy(s->slot + start, buf, (size_t)n1 * sizeof(struct srow)); + memcpy(s->slot, buf + n1, (size_t)n2 * sizeof(struct srow)); + if (buf != tmp) + free(buf); + } } if (!t->alternate && t->prompt_y >= top && t->prompt_y <= bottom) { @@ -1047,12 +1185,22 @@ static bool reflow_append(struct reflow_target *r, const struct cell *cells, int int cursor, int saved) { bool has_wide = false; - for (int i = 0; i < len; i++) { - if (cells[i].flags & (CELL_WIDE | CELL_TRAIL | CELL_WRAP_PAD)) { + const uint16_t wide_flags = CELL_WIDE | CELL_TRAIL | CELL_WRAP_PAD; + int i = 0; + for (; i + 4 <= len; i += 4) { + if ((cells[i].flags | cells[i + 1].flags | cells[i + 2].flags | cells[i + 3].flags) & wide_flags) { has_wide = true; break; } } + if (!has_wide) { + for (; i < len; i++) { + if (cells[i].flags & wide_flags) { + has_wide = true; + break; + } + } + } bool ok = has_wide ? reflow_append_wide(r, cells, len, cursor, saved) : reflow_append_span(r, cells, len, cursor, saved); if (!ok) @@ -1104,8 +1252,7 @@ static int pull_history_rows(struct term *t, struct screen *dst_scr, int cols, i if (got < cols) { fill_cells(dst->cells + got, cols - got, TERM_COLOR_FG, TERM_COLOR_BG); if (wrapped) { - for (int x = got; x < cols; x++) - dst->cells[x].flags |= CELL_WRAP_PAD; + set_cells_wrap_pad(dst->cells + got, cols - got); got = cols; } } @@ -1213,8 +1360,11 @@ static bool term_resize_internal(struct term *t, int cols, int rows) pull = pull_history_rows(t, &new_scr, cols, rows - old_rows); int y = pull; + int p_curr = active_scr->top; for (int i = 0; i < old_rows && y < rows; i++, y++) { - struct srow *old_r = screen_get_row(active_scr, i); + struct srow *old_r = &active_scr->slot[p_curr]; + srow_materialize_text(old_r); + p_curr = (p_curr + 1 == old_rows) ? 0 : p_curr + 1; copy_srow_resized(&new_scr.slot[y], old_r, old_cols, cols); } int first_new = y; @@ -1264,11 +1414,14 @@ static bool term_resize_internal(struct term *t, int cols, int rows) } int active = old_rows; - while (active > t->cursor_y + 1 && active > t->saved.y + 1) { - struct srow *sr = screen_get_row(&t->scr, active - 1); - if (sr->len || (sr->flags & RF_WRAPPED)) + int min_active = t->cursor_y >= t->saved.y ? t->cursor_y + 1 : t->saved.y + 1; + int p_scan = phys_row(t, old_rows - 1); + while (active > min_active) { + const struct srow *sr = &t->scr.slot[p_scan]; + if (sr->len != 0 || (sr->flags & RF_WRAPPED) != 0) break; active--; + p_scan = (p_scan == 0) ? old_rows - 1 : p_scan - 1; } struct reflow_target r = { @@ -1298,8 +1451,11 @@ static bool term_resize_internal(struct term *t, int cols, int rows) size_t line_cap = (size_t)old_cols + (size_t)t->saved.x + 2; struct cell *line_buf = grow(&t->resize.line, line_cap * sizeof(struct cell)); + int p_src = t->scr.top; for (int y = 0; y < active; y++) { - struct srow *src = screen_get_row(&t->scr, y); + struct srow *src = &t->scr.slot[p_src]; + srow_materialize_text(src); + p_src = (p_src + 1 == old_rows) ? 0 : p_src + 1; int len = src->len; bool wrapped = (src->flags & RF_WRAPPED) != 0; @@ -1488,7 +1644,11 @@ bool term_resize(struct term *t, struct term_vector2i size) int first_tab = (t->tabs_cap + 7) & ~7; if (first_tab == 0) first_tab = 8; - for (int c = first_tab; c < new_cap; c += 8) + uint64_t tab8 = 1; + int c = first_tab; + for (; c + 8 <= new_cap; c += 8) + memcpy(new_tabs + c, &tab8, sizeof(tab8)); + if (c < new_cap) new_tabs[c] = 1; t->tabs = new_tabs; t->tabs_cap = new_cap; diff --git a/src/screen.h b/src/screen.h index 0110049..50e18c6 100644 --- a/src/screen.h +++ b/src/screen.h @@ -42,6 +42,64 @@ static inline int phys_row(const struct term *t, int y) return p >= t->scr.rows ? p - t->scr.rows : p; } +#if defined(TERM_KERNEL_AVX2) +TERM_TARGET_AVX2 +static inline void set_cells_wrap_pad_avx2(struct cell *cells, int count) +{ + const __m256i mask = _mm256_setr_epi32(0, 0, 0, (int)CELL_WRAP_PAD, + 0, 0, 0, (int)CELL_WRAP_PAD); + int i = 0; + for (; i + 4 <= count; i += 4) { + __m256i v0 = _mm256_loadu_si256((const __m256i *)(cells + i)); + __m256i v1 = _mm256_loadu_si256((const __m256i *)(cells + i + 2)); + v0 = _mm256_or_si256(v0, mask); + v1 = _mm256_or_si256(v1, mask); + _mm256_storeu_si256((__m256i *)(cells + i), v0); + _mm256_storeu_si256((__m256i *)(cells + i + 2), v1); + } + for (; i < count; i++) + cells[i].flags |= CELL_WRAP_PAD; +} +#endif + +static inline void set_cells_wrap_pad(struct cell *cells, int count) +{ + if (count <= 0) + return; +#if defined(TERM_KERNEL_AVX2) + if (term_use_avx2() && count >= 4) { + set_cells_wrap_pad_avx2(cells, count); + return; + } +#endif +#if defined(__x86_64__) || defined(_M_X64) + const __m128i mask = _mm_setr_epi32(0, 0, 0, (int)CELL_WRAP_PAD); + int i = 0; + for (; i + 2 <= count; i += 2) { + __m128i v0 = _mm_loadu_si128((const __m128i *)(cells + i)); + __m128i v1 = _mm_loadu_si128((const __m128i *)(cells + i + 1)); + _mm_storeu_si128((__m128i *)(cells + i), _mm_or_si128(v0, mask)); + _mm_storeu_si128((__m128i *)(cells + i + 1), _mm_or_si128(v1, mask)); + } + for (; i < count; i++) + cells[i].flags |= CELL_WRAP_PAD; +#elif defined(__aarch64__) + const uint32x4_t mask = { 0, 0, 0, CELL_WRAP_PAD }; + int i = 0; + for (; i + 2 <= count; i += 2) { + uint32x4_t v0 = vld1q_u32((const uint32_t *)(cells + i)); + uint32x4_t v1 = vld1q_u32((const uint32_t *)(cells + i + 1)); + vst1q_u32((uint32_t *)(cells + i), vorrq_u32(v0, mask)); + vst1q_u32((uint32_t *)(cells + i + 1), vorrq_u32(v1, mask)); + } + for (; i < count; i++) + cells[i].flags |= CELL_WRAP_PAD; +#else + for (int i = 0; i < count; i++) + cells[i].flags |= CELL_WRAP_PAD; +#endif +} + static inline void init_ascii_text_row(struct text_row *tr, uint8_t *text, uint16_t len) { tr->text = text; diff --git a/src/selection.c b/src/selection.c index 0eda34f..b05a43c 100644 --- a/src/selection.c +++ b/src/selection.c @@ -187,8 +187,7 @@ static const struct cell *line_seg_cells(const struct lines *L, uint64_t id, int if (got < L->cols) { fill_cells(buf + got, L->cols - got, TERM_COLOR_FG, TERM_COLOR_BG); if (wrapped) - for (int x = got; x < L->cols; x++) - buf[x].flags |= CELL_WRAP_PAD; + set_cells_wrap_pad(buf + got, L->cols - got); } if (out_wrapped) *out_wrapped = wrapped; @@ -678,8 +677,7 @@ static char *range_text_inner(const struct term *t, if (got < t->cols) { fill_cells(row_buf + got, t->cols - got, TERM_COLOR_FG, TERM_COLOR_BG); if (row_wrapped) - for (int x = got; x < t->cols; x++) - row_buf[x].flags |= CELL_WRAP_PAD; + set_cells_wrap_pad(row_buf + got, t->cols - got); } cells = row_buf; } -- cgit v1.2.3