Optimize: get glyph uv rects during layouts instead of in tesselation
This allows them to be cached, saving around 20% total CPU. It also makes the code more nicely structured
This commit is contained in:
parent
22cd1a8e10
commit
0802a9d9c0
4 changed files with 71 additions and 46 deletions
|
@ -634,36 +634,38 @@ impl Tessellator {
|
|||
out.reserve_triangles(num_chars * 2);
|
||||
out.reserve_vertices(num_chars * 4);
|
||||
|
||||
let tex_w = fonts.texture().width as f32;
|
||||
let tex_h = fonts.texture().height as f32;
|
||||
let inv_tex_w = 1.0 / fonts.texture().width as f32;
|
||||
let inv_tex_h = 1.0 / fonts.texture().height as f32;
|
||||
|
||||
let clip_rect = self.clip_rect.expand(2.0); // Some fudge to handle letters that are slightly larger than expected.
|
||||
|
||||
let font = &fonts[galley.text_style];
|
||||
let mut chars = galley.text.chars();
|
||||
for line in &galley.rows {
|
||||
let line_min_y = pos.y + line.y_min;
|
||||
let line_max_y = line_min_y + font.row_height();
|
||||
let is_line_visible = line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y;
|
||||
for row in &galley.rows {
|
||||
let row_min_y = pos.y + row.y_min;
|
||||
let row_max_y = pos.y + row.y_max;
|
||||
let is_line_visible = row_max_y >= clip_rect.min.y && row_min_y <= clip_rect.max.y;
|
||||
|
||||
for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) {
|
||||
let c = chars.next().unwrap();
|
||||
if self.options.coarse_tessellation_culling && !is_line_visible {
|
||||
// culling individual lines of text is important, since a single `Shape::Text`
|
||||
// can span hundreds of lines.
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling && !is_line_visible {
|
||||
// culling individual lines of text is important, since a single `Shape::Text`
|
||||
// can span hundreds of lines.
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(glyph) = font.uv_rect(c) {
|
||||
let mut left_top = pos + glyph.offset + vec2(*x_offset, line.y_min);
|
||||
left_top.x = font.round_to_pixel(left_top.x); // Pixel-perfection.
|
||||
left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection.
|
||||
for (x_offset, uv_rect) in row.x_offsets.iter().zip(&row.uv_rects) {
|
||||
if let Some(glyph) = uv_rect {
|
||||
let mut left_top = pos + glyph.offset + vec2(*x_offset, row.y_min);
|
||||
left_top.x = fonts.round_to_pixel(left_top.x); // Pixel-perfection.
|
||||
left_top.y = fonts.round_to_pixel(left_top.y); // Pixel-perfection.
|
||||
|
||||
let rect = Rect::from_min_max(left_top, left_top + glyph.size);
|
||||
let uv = Rect::from_min_max(
|
||||
pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h),
|
||||
pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h),
|
||||
pos2(
|
||||
glyph.min.0 as f32 * inv_tex_w,
|
||||
glyph.min.1 as f32 * inv_tex_h,
|
||||
),
|
||||
pos2(
|
||||
glyph.max.0 as f32 * inv_tex_w,
|
||||
glyph.max.1 as f32 * inv_tex_h,
|
||||
),
|
||||
);
|
||||
|
||||
if fake_italics {
|
||||
|
@ -698,12 +700,7 @@ impl Tessellator {
|
|||
}
|
||||
}
|
||||
}
|
||||
if line.ends_with_newline {
|
||||
let newline = chars.next().unwrap();
|
||||
debug_assert_eq!(newline, '\n');
|
||||
}
|
||||
}
|
||||
assert_eq!(chars.next(), None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use emath::{vec2, Vec2};
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct UvRect {
|
||||
/// X/Y offset for nice rendering (unit: points).
|
||||
pub offset: Vec2,
|
||||
|
@ -220,7 +220,7 @@ impl Font {
|
|||
self.text_style
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
@ -310,6 +310,7 @@ impl Font {
|
|||
let x_offsets = self.layout_single_row_fragment(&text);
|
||||
let row = Row {
|
||||
x_offsets,
|
||||
uv_rects: vec![], // will be filled in later
|
||||
y_min: 0.0,
|
||||
y_max: self.row_height(),
|
||||
ends_with_newline: false,
|
||||
|
@ -322,8 +323,7 @@ impl Font {
|
|||
rows: vec![row],
|
||||
size,
|
||||
};
|
||||
galley.sanity_check();
|
||||
galley
|
||||
self.finalize_galley(galley)
|
||||
}
|
||||
|
||||
/// Always returns at least one row.
|
||||
|
@ -389,6 +389,7 @@ impl Font {
|
|||
if text.is_empty() {
|
||||
rows.push(Row {
|
||||
x_offsets: vec![first_row_indentation],
|
||||
uv_rects: vec![],
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + row_height,
|
||||
ends_with_newline: false,
|
||||
|
@ -396,6 +397,7 @@ impl Font {
|
|||
} else if text.ends_with('\n') {
|
||||
rows.push(Row {
|
||||
x_offsets: vec![0.0],
|
||||
uv_rects: vec![],
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + row_height,
|
||||
ends_with_newline: false,
|
||||
|
@ -415,8 +417,7 @@ impl Font {
|
|||
rows,
|
||||
size,
|
||||
};
|
||||
galley.sanity_check();
|
||||
galley
|
||||
self.finalize_galley(galley)
|
||||
}
|
||||
|
||||
/// A paragraph is text with no line break character in it.
|
||||
|
@ -431,6 +432,7 @@ impl Font {
|
|||
if text.is_empty() {
|
||||
return vec![Row {
|
||||
x_offsets: vec![first_row_indentation],
|
||||
uv_rects: vec![],
|
||||
y_min: 0.0,
|
||||
y_max: self.row_height(),
|
||||
ends_with_newline: false,
|
||||
|
@ -461,28 +463,26 @@ impl Font {
|
|||
{
|
||||
// Allow the first row to be completely empty, because we know there will be more space on the next row:
|
||||
assert_eq!(row_start_idx, 0);
|
||||
let row = Row {
|
||||
out_rows.push(Row {
|
||||
x_offsets: vec![first_row_indentation],
|
||||
uv_rects: vec![],
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
};
|
||||
row.sanity_check();
|
||||
out_rows.push(row);
|
||||
});
|
||||
cursor_y = self.round_to_pixel(cursor_y + self.row_height());
|
||||
first_row_indentation = 0.0; // Continue all other rows as if there is no indentation
|
||||
} else if let Some(last_kept_index) = row_break_candidates.get() {
|
||||
let row = Row {
|
||||
out_rows.push(Row {
|
||||
x_offsets: full_x_offsets[row_start_idx..=last_kept_index + 1]
|
||||
.iter()
|
||||
.map(|x| first_row_indentation + x - row_start_x)
|
||||
.collect(),
|
||||
uv_rects: vec![], // Will be filled in later!
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
};
|
||||
row.sanity_check();
|
||||
out_rows.push(row);
|
||||
});
|
||||
|
||||
row_start_idx = last_kept_index + 1;
|
||||
row_start_x = first_row_indentation + full_x_offsets[row_start_idx];
|
||||
|
@ -495,21 +495,39 @@ impl Font {
|
|||
}
|
||||
|
||||
if row_start_idx + 1 < full_x_offsets.len() {
|
||||
let row = Row {
|
||||
out_rows.push(Row {
|
||||
x_offsets: full_x_offsets[row_start_idx..]
|
||||
.iter()
|
||||
.map(|x| first_row_indentation + x - row_start_x)
|
||||
.collect(),
|
||||
uv_rects: vec![], // Will be filled in later!
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
};
|
||||
row.sanity_check();
|
||||
out_rows.push(row);
|
||||
});
|
||||
}
|
||||
|
||||
out_rows
|
||||
}
|
||||
|
||||
fn finalize_galley(&self, mut galley: Galley) -> Galley {
|
||||
let mut chars = galley.text.chars();
|
||||
for row in &mut galley.rows {
|
||||
row.uv_rects.clear();
|
||||
row.uv_rects.reserve(row.char_count_excluding_newline());
|
||||
for _ in 0..row.char_count_excluding_newline() {
|
||||
let c = chars.next().unwrap();
|
||||
row.uv_rects.push(self.uv_rect(c));
|
||||
}
|
||||
if row.ends_with_newline {
|
||||
let newline = chars.next().unwrap();
|
||||
assert_eq!(newline, '\n');
|
||||
}
|
||||
}
|
||||
assert_eq!(chars.next(), None);
|
||||
galley.sanity_check();
|
||||
galley
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of good places to break a long row of text.
|
||||
|
|
|
@ -247,6 +247,7 @@ impl Fonts {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pixels_per_point(&self) -> f32 {
|
||||
self.pixels_per_point
|
||||
}
|
||||
|
@ -255,6 +256,11 @@ impl Fonts {
|
|||
&self.definitions
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Call each frame to get the latest available font texture data.
|
||||
pub fn texture(&self) -> Arc<Texture> {
|
||||
let atlas = self.atlas.lock();
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
//! and the start of the second row.
|
||||
//! [`CCursor::prefer_next_row`] etc selects which.
|
||||
|
||||
use super::cursor::*;
|
||||
use super::{cursor::*, font::UvRect};
|
||||
use emath::{pos2, NumExt, Rect, Vec2};
|
||||
|
||||
/// A collection of text locked into place.
|
||||
|
@ -52,6 +52,9 @@ pub struct Row {
|
|||
/// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1`
|
||||
pub x_offsets: Vec<f32>,
|
||||
|
||||
/// Per-character. Used when rendering.
|
||||
pub uv_rects: Vec<Option<UvRect>>,
|
||||
|
||||
/// Top of the row, offset within the Galley.
|
||||
/// Unit: points.
|
||||
pub y_min: f32,
|
||||
|
@ -71,6 +74,7 @@ pub struct Row {
|
|||
impl Row {
|
||||
pub fn sanity_check(&self) {
|
||||
assert!(!self.x_offsets.is_empty());
|
||||
assert!(self.x_offsets.len() == self.uv_rects.len() + 1);
|
||||
}
|
||||
|
||||
/// Excludes the implicit `\n` after the `Row`, if any.
|
||||
|
|
Loading…
Reference in a new issue