8000 Optimizing re-layout of 1MB+ pieces of text in a `TextEdit` · Issue #3086 · emilk/egui · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Optimizing re-layout of 1MB+ pieces of text in a TextEdit #3086
@NickHu

Description

@NickHu

The problem

egui's TextEdit becomes sluggish when it holds large amounts of text; probably, this is due to it rendering all of the text without doing anything clever to cull text which doesn't make it on to the screen (apologies if this is the wrong terminology, I'm not an expert in graphics programming). Even without doing anything clever, there seem to be some optimisation opportunities inside the code, but I wanted to seek some guidance on whether this is worth doing or not before committing to working on this.

At least, with my test program (see end, adapted from #2799), when there are 15 million characters inside the widget, it is noticably laggy to use (>1s per character insert, compiled with --release).

I've done some crude println! profiling with std::time::Instant::now() to find the hot loops, as I will detail below.

Related issues: #1196 #2799 #2906

Profiling

Here are the measured slow parts of the result of inserting one 'a' into the middle of the buffer (took around ~2s on release mode):

for chr in job.text[byte_range.clone()].chars() {
if job.break_on_newline && chr == '\n' {
out_paragraphs.push(Paragraph::default());
paragraph = out_paragraphs.last_mut().unwrap();
paragraph.empty_paragraph_height = font_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs?
} else {
let (font_impl, glyph_info) = font.glyph_info_and_font_impl(chr);
if let Some(font_impl) = font_impl {
if let Some(last_glyph_id) = last_glyph_id {
paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id);
}
}
paragraph.glyphs.push(Glyph {
chr,
pos: pos2(paragraph.cursor_x, f32::NAN),
size: vec2(glyph_info.advance_width, glyph_info.row_height),
ascent: glyph_info.ascent,
uv_rect: glyph_info.uv_rect,
section_index,
});
paragraph.cursor_x += glyph_info.advance_width;
paragraph.cursor_x = font.round_to_pixel(paragraph.cursor_x);
last_glyph_id = Some(glyph_info.id);
}
}

Took ~0.5s to complete the loop, and reallocated paragraph.glyphs 23 times. I think with a call to Vec::reserve_exact the allocator pressure can be eliminated entirely, as it should be possible to determine how big each vector is supposed to be.

let mut rows = rows_from_paragraphs(fonts, paragraphs, &job);

Took ~0.5s to complete.

for row in &mut rows {
row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds);
num_vertices += row.visuals.mesh.vertices.len();
num_indices += row.visuals.mesh.indices.len();
}

Took ~1s to complete.

Test program

use eframe::egui::{self, CentralPanel, TextEdit};

fn main() -> Result<(), eframe::Error> {
    let options = eframe::NativeOptions {
        ..Default::default()
    };

    eframe::run_native(
        "editor big file test",
        options,
        Box::new(|_cc| Box::<MyApp>::new(MyApp::new())),
    )
}

struct MyApp {
    text: String,
}

impl MyApp {
    fn new() -> Self {
        let bytes = (0..500000).flat_map(|_| (0u8..10));
        let string: String = bytes.map(|b| format!(" {b:02x}")).collect();
        MyApp { text: string }
    }
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        CentralPanel::default().show(ctx, |ui| {
            let code_editor = TextEdit::multiline(&mut self.text)
                .code_editor()
                .desired_width(f32::INFINITY)
                .desired_rows(40);
            let _output = code_editor.show(ui);
        });
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueGood for newcomersperformanceLower CPU/GPU usage (optimize)textProblems related to text

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0