Terminal: Optimize printing a lot

Terminal used to run `yes` at around 400 lines per second

This patch pumps that to over 100'000 lines per second!

There are 2 optimizations done:
  1. only invalidate window once after rendering is done
  2. if printing more than `rows()` newlines skip prior data
This commit is contained in:
Bananymous 2024-09-15 03:09:10 +03:00
parent 7feb4c4ebd
commit c3040a04a3
2 changed files with 97 additions and 22 deletions

View File

@ -196,14 +196,69 @@ void Terminal::show_cursor()
bool Terminal::read_shell()
{
char buffer[128];
ssize_t nread = read(m_shell_info.pts_master, buffer, sizeof(buffer) - 1);
char buffer[512];
ssize_t nread = read(m_shell_info.pts_master, buffer, sizeof(buffer));
if (nread < 0)
dwarnln("read: {}", strerror(errno));
if (nread <= 0)
return false;
for (ssize_t i = 0; i < nread; i++)
putchar(buffer[i]);
Rectangle should_invalidate;
ssize_t i = 0;
while (i < nread)
{
// all ansi escape codes must be handled
if (buffer[i] == '\e')
{
while (i < nread)
{
char ch = buffer[i++];
should_invalidate = should_invalidate.get_bounding_box(putchar(ch));
if (isalpha(ch))
break;
}
continue;
}
// find the next ansi escape code or end of buffer
size_t non_ansi_end = i;
while (non_ansi_end < nread && buffer[non_ansi_end] != '\e')
non_ansi_end++;
// we only need to process maximum of `rows()` newlines.
// anything before that would get overwritten anyway
size_t start = non_ansi_end;
size_t newline_count = 0;
while (start > i && newline_count < rows())
newline_count += (buffer[--start] == '\n');
// do possible scrolling already in here, so `putchar()` doesnt
// have to scroll up to `rows()` times
if (m_cursor.y + newline_count >= rows())
{
const uint32_t scroll = m_cursor.y + newline_count - rows() + 1;
m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height());
m_window->fill_rect(0, m_window->height() - scroll * m_font.height(), m_window->width(), scroll * m_font.height(), m_bg_color);
should_invalidate = { 0, 0, m_window->width(), m_window->height() };
}
i = start;
for (i = start; i < non_ansi_end; i++)
should_invalidate = should_invalidate.get_bounding_box(putchar(buffer[i]));
}
if (should_invalidate.height && should_invalidate.width)
{
m_window->invalidate(
should_invalidate.x,
should_invalidate.y,
should_invalidate.width,
should_invalidate.height
);
}
return true;
}
@ -361,7 +416,7 @@ void Terminal::handle_csi(char ch)
m_state = State::Normal;
}
void Terminal::putchar(uint8_t ch)
Rectangle Terminal::putchar(uint8_t ch)
{
if (m_state == State::ESC)
{
@ -369,14 +424,14 @@ void Terminal::putchar(uint8_t ch)
{
dprintln("unknown escape character 0x{2H}", ch);
m_state = State::Normal;
return;
return {};
}
m_state = State::CSI;
m_csi_info.index = 0;
m_csi_info.fields[0] = -1;
m_csi_info.fields[1] = -1;
m_csi_info.question = false;
return;
return {};
}
if (m_state == State::CSI)
@ -385,10 +440,10 @@ void Terminal::putchar(uint8_t ch)
{
dprintln("invalid CSI 0x{2H}", ch);
m_state = State::Normal;
return;
return {};
}
handle_csi(ch);
return;
return {};
}
m_utf8_bytes[m_utf8_index++] = ch;
@ -398,10 +453,10 @@ void Terminal::putchar(uint8_t ch)
{
dwarnln("invalid utf8 leading byte 0x{2H}", ch);
m_utf8_index = 0;
return;
return {};
}
if (m_utf8_index < utf8_len)
return;
return {};
const uint32_t codepoint = BAN::UTF8::to_codepoint(m_utf8_bytes);
m_utf8_index = 0;
@ -421,9 +476,11 @@ void Terminal::putchar(uint8_t ch)
*--ptr = '\0';
dwarnln("invalid utf8 {}", utf8_hex);
return;
return {};
}
Rectangle should_invalidate;
switch (codepoint)
{
case '\e':
@ -449,7 +506,7 @@ void Terminal::putchar(uint8_t ch)
m_window->fill_rect(cell_x, cell_y, cell_w, cell_h, m_bg_color);
m_window->draw_character(codepoint, m_font, cell_x, cell_y, m_fg_color);
m_window->invalidate(cell_x, cell_y, cell_w, cell_h);
should_invalidate = { cell_x, cell_y, cell_w, cell_h };
m_cursor.x++;
break;
}
@ -461,14 +518,10 @@ void Terminal::putchar(uint8_t ch)
m_cursor.y++;
}
if (m_cursor.y >= rows())
{
uint32_t scroll = m_cursor.y - rows() + 1;
m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height());
m_window->fill_rect(0, m_window->height() - scroll * m_font.height(), m_window->width(), scroll * m_font.height(), m_bg_color);
m_window->invalidate();
}
// scrolling is already handled in `read_shell()`
ASSERT(m_cursor.y < rows());
return should_invalidate;
}
void Terminal::on_key_event(LibGUI::EventPacket::KeyEvent event)

View File

@ -4,6 +4,28 @@
#include <LibGUI/Window.h>
struct Rectangle
{
uint32_t x { 0 };
uint32_t y { 0 };
uint32_t width { 0 };
uint32_t height { 0 };
Rectangle get_bounding_box(Rectangle other) const
{
const auto min_x = BAN::Math::min(x, other.x);
const auto min_y = BAN::Math::min(y, other.y);
const auto max_x = BAN::Math::max(x + width, other.x + other.width);
const auto max_y = BAN::Math::max(y + height, other.y + other.height);
return Rectangle {
.x = min_x,
.y = min_y,
.width = max_x - min_x,
.height = max_y - min_y,
};
}
};
class Terminal
{
public:
@ -15,7 +37,7 @@ public:
private:
void handle_csi(char ch);
void handle_sgr();
void putchar(uint8_t ch);
Rectangle putchar(uint8_t ch);
bool read_shell();
void hide_cursor();