summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 14:10:31 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 14:10:31 +0300
commit7633ab2c60b40bd5aa67ef391b2a0d149685742f (patch)
tree37d4f89722605a177715f400eb103382ff08e2c3 /src
parent274abc013e4b914b005191a7f74eaf2961a1e59e (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/screen.c286
-rw-r--r--src/screen.h58
-rw-r--r--src/selection.c6
3 files changed, 283 insertions, 67 deletions
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;
}