Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

epaint: Memoize individual lines during text layout #5411

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
622b848
Fix typo
afishhh Nov 28, 2024
150c0f6
Cache individual lines of text in GalleyCache
afishhh Nov 28, 2024
db32a1e
Make Galleys share Rows and store their offsets
afishhh Nov 28, 2024
4e3f162
Fix lints
afishhh Nov 29, 2024
3de1723
Don't add leading space to more than one split layout section
afishhh Nov 29, 2024
f028154
Move cached-multiline-layout code into a helper function
afishhh Nov 29, 2024
bc86bec
Properly handle row repositioning
afishhh Nov 29, 2024
abbc561
Correctly handle empty lines
afishhh Nov 30, 2024
6d6bc3b
Respect first_row_min_height during multiline Galley layout
afishhh Nov 30, 2024
6147ff3
Round `PlacedRow` positions to pixels during multiline layout
afishhh Nov 30, 2024
66c83c3
Move `ends_with_newline` back into `Row`
afishhh Nov 30, 2024
1be24ba
Respect `LayoutJob::round_output_size_to_nearest_ui_point`
afishhh Nov 30, 2024
fd8413c
Simplify `layout_multiline` `loop` loop into a `while` loop
afishhh Nov 30, 2024
bbe5662
Fix `Row::ends_with_newline` docs, explain skipping of last galley row
afishhh Nov 30, 2024
110a9c3
Move some `PlacedRow` methods back to `Row`
afishhh Nov 30, 2024
139f286
Replace a hack with a proper implementation
afishhh Nov 30, 2024
e15b34b
Slightly simplify `paint_text_selection` code
afishhh Dec 1, 2024
c6592ec
Fix nits
afishhh Dec 4, 2024
25da822
Fix text horizontal alignment
afishhh Dec 5, 2024
40f237d
Add comment and check for newline before multiline layout
afishhh Dec 5, 2024
17a5f1f
Fix incorrect behavior with `LayoutJob::max_rows`
afishhh Dec 5, 2024
c094ee8
Merge branch 'emilk:master' into cache_galley_lines
afishhh Dec 7, 2024
3e1ed18
Add benchmark
afishhh Dec 11, 2024
ec9a408
Merge branch 'master' into cache_galley_lines
afishhh Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,7 @@ dependencies = [
"egui",
"egui_extras",
"egui_kittest",
"rand",
"serde",
"unicode_names2",
"wgpu",
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/text_selection/accesskit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn update_accesskit_for_text_widget(
let row_id = parent_id.with(row_index);
ctx.accesskit_node_builder(row_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
let rect = row.rect.translate(galley_pos.to_vec2());
let rect = row.rect().translate(galley_pos.to_vec2());
builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(),
y0: rect.min.y.into(),
Expand Down Expand Up @@ -74,14 +74,14 @@ pub fn update_accesskit_for_text_widget(
let old_len = value.len();
value.push(glyph.chr);
character_lengths.push((value.len() - old_len) as _);
character_positions.push(glyph.pos.x - row.rect.min.x);
character_positions.push(glyph.pos.x - row.pos.x);
character_widths.push(glyph.advance_width);
}

if row.ends_with_newline {
value.push('\n');
character_lengths.push(1);
character_positions.push(row.rect.max.x - row.rect.min.x);
character_positions.push(row.size.x);
character_widths.push(0.0);
}
word_lengths.push((character_lengths.len() - last_word_start) as _);
Expand Down
9 changes: 6 additions & 3 deletions crates/egui/src/text_selection/label_text_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ impl LabelSelectionState {
if let epaint::Shape::Text(text_shape) = &mut shape.shape {
let galley = Arc::make_mut(&mut text_shape.galley);
for row_selection in row_selections {
if let Some(row) = galley.rows.get_mut(row_selection.row) {
if let Some(placed_row) =
galley.rows.get_mut(row_selection.row)
{
let row = Arc::make_mut(&mut placed_row.row);
for vertex_index in row_selection.vertex_indices {
if let Some(vertex) = row
.visuals
Expand Down Expand Up @@ -659,8 +662,8 @@ fn selected_text(galley: &Galley, cursor_range: &CursorRange) -> String {
}

fn estimate_row_height(galley: &Galley) -> f32 {
if let Some(row) = galley.rows.first() {
row.rect.height()
if let Some(placed_row) = galley.rows.first() {
placed_row.height()
} else {
galley.size().y
}
Expand Down
9 changes: 5 additions & 4 deletions crates/egui/src/text_selection/visuals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ pub fn paint_text_selection(
let max = max.rcursor;

for ri in min.row..=max.row {
let row = &mut galley.rows[ri];
let row = Arc::make_mut(&mut galley.rows[ri].row);

let left = if ri == min.row {
row.x_offset(min.column)
} else {
row.rect.left()
0.0
};
let right = if ri == max.row {
row.x_offset(max.column)
Expand All @@ -45,10 +46,10 @@ pub fn paint_text_selection(
} else {
0.0
};
row.rect.right() + newline_size
row.size.x + newline_size
};

let rect = Rect::from_min_max(pos2(left, row.min_y()), pos2(right, row.max_y()));
let rect = Rect::from_min_max(pos2(left, 0.0), pos2(right, row.size.y));
let mesh = &mut row.visuals.mesh;

// Time to insert the selection rectangle into the row mesh.
Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,8 @@ impl WidgetText {
Self::RichText(text) => text.font_height(fonts, style),
Self::LayoutJob(job) => job.font_height(fonts),
Self::Galley(galley) => {
if let Some(row) = galley.rows.first() {
row.height()
if let Some(placed_row) = galley.rows.first() {
placed_row.height()
} else {
galley.size().y
}
Expand Down
10 changes: 5 additions & 5 deletions crates/egui/src/widgets/label.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::sync::Arc;

use crate::{
epaint, pos2, text_selection, vec2, Align, Direction, FontSelection, Galley, Pos2, Response,
Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
epaint, pos2, text_selection, Align, Direction, FontSelection, Galley, Pos2, Response, Sense,
Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
};

use self::text_selection::LabelSelectionState;
Expand Down Expand Up @@ -194,10 +194,10 @@ impl Label {
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
assert!(!galley.rows.is_empty(), "Galleys are never empty");
// collect a response from many rows:
let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y));
let rect = galley.rows[0].rect().translate(pos.to_vec2());
let mut response = ui.allocate_rect(rect, sense);
for row in galley.rows.iter().skip(1) {
let rect = row.rect.translate(vec2(pos.x, pos.y));
for placed_row in galley.rows.iter().skip(1) {
let rect = placed_row.rect().translate(pos.to_vec2());
response |= ui.allocate_rect(rect, sense);
}
(pos, galley, response)
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ criterion.workspace = true
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
wgpu = { workspace = true, features = ["metal"] }
egui = { workspace = true, features = ["default_fonts"] }
rand = "0.8"

[[bench]]
name = "benchmark"
Expand Down
31 changes: 31 additions & 0 deletions crates/egui_demo_lib/benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::fmt::Write as _;

use criterion::{criterion_group, criterion_main, Criterion};

use egui::epaint::TextShape;
use egui_demo_lib::LOREM_IPSUM_LONG;
use rand::Rng as _;

pub fn criterion_benchmark(c: &mut Criterion) {
use egui::RawInput;
Expand Down Expand Up @@ -122,6 +125,34 @@ pub fn criterion_benchmark(c: &mut Criterion) {
});
});

c.bench_function("text_layout_cached_with_modify", |b| {
const MAX_REMOVED_BYTES: usize = 5000;

let mut string = String::new();
// 2000 lines * 200 bytes * ~3 characters = 1.2MB
string.reserve(2000 * 200 * 3 + 2000);
for _ in 0..2000 {
for i in 0..200u8 {
write!(string, "{i:02X} ").unwrap();
}
string.push('\n');
}

let mut rng = rand::thread_rng();
b.iter(|| {
fonts.begin_pass(pixels_per_point, max_texture_side);
let mut temp_string = String::with_capacity(string.len());
let modified_start = rng.gen_range(0..string.len());
let max_end = (modified_start + MAX_REMOVED_BYTES).min(string.len());
let modified_end = rng.gen_range(modified_start..max_end);

temp_string.push_str(&string[..modified_start]);
temp_string.push_str(&string[modified_end..]);

fonts.layout(temp_string, font_id.clone(), text_color, wrap_width);
});
});

let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width);
let font_image_size = fonts.font_image_size();
let prepared_discs = fonts.texture_atlas().lock().prepared_discs();
Expand Down
3 changes: 2 additions & 1 deletion crates/epaint/src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ impl Shape {

// Scale text:
let galley = Arc::make_mut(&mut text_shape.galley);
for row in &mut galley.rows {
for placed_row in &mut galley.rows {
let row = Arc::make_mut(&mut placed_row.row);
row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds;
for v in &mut row.visuals.mesh.vertices {
v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y);
Expand Down
3 changes: 2 additions & 1 deletion crates/epaint/src/shape_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ pub fn adjust_colors(

if !galley.is_empty() {
let galley = std::sync::Arc::make_mut(galley);
for row in &mut galley.rows {
for placed_row in &mut galley.rows {
let row = Arc::make_mut(&mut placed_row.row);
for vertex in &mut row.visuals.mesh.vertices {
adjust_color(&mut vertex.color);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/epaint/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl AllocInfo {
+ galley.rows.iter().map(Self::from_galley_row).sum()
}

fn from_galley_row(row: &crate::text::Row) -> Self {
fn from_galley_row(row: &crate::text::PlacedRow) -> Self {
Self::from_mesh(&row.visuals.mesh) + Self::from_slice(&row.glyphs)
}

Expand Down
6 changes: 4 additions & 2 deletions crates/epaint/src/tessellator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1804,11 +1804,13 @@ impl Tessellator {
continue;
}

let final_row_pos = galley_pos + row.pos.to_vec2();

let mut row_rect = row.visuals.mesh_bounds;
if *angle != 0.0 {
row_rect = row_rect.rotate_bb(rotator);
}
row_rect = row_rect.translate(galley_pos.to_vec2());
row_rect = row_rect.translate(final_row_pos.to_vec2());

if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
// culling individual lines of text is important, since a single `Shape::Text`
Expand Down Expand Up @@ -1857,7 +1859,7 @@ impl Tessellator {
};

Vertex {
pos: galley_pos + offset,
pos: final_row_pos + offset,
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
color,
}
Expand Down
Loading